michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* Call context. */ michael@0: michael@0: #include "xpcprivate.h" michael@0: #include "jswrapper.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace xpc; michael@0: using namespace JS; michael@0: michael@0: #define IS_TEAROFF_CLASS(clazz) ((clazz) == &XPC_WN_Tearoff_JSClass) michael@0: michael@0: XPCCallContext::XPCCallContext(XPCContext::LangType callerLanguage, michael@0: JSContext* cx, michael@0: HandleObject obj /* = nullptr */, michael@0: HandleObject funobj /* = nullptr */, michael@0: HandleId name /* = JSID_VOID */, michael@0: unsigned argc /* = NO_ARGS */, michael@0: jsval *argv /* = nullptr */, michael@0: jsval *rval /* = nullptr */) michael@0: : mAr(cx), michael@0: mState(INIT_FAILED), michael@0: mXPC(nsXPConnect::XPConnect()), michael@0: mXPCContext(nullptr), michael@0: mJSContext(cx), michael@0: mCallerLanguage(callerLanguage), michael@0: mFlattenedJSObject(cx), michael@0: mWrapper(nullptr), michael@0: mTearOff(nullptr), michael@0: mName(cx) michael@0: { michael@0: MOZ_ASSERT(cx); michael@0: MOZ_ASSERT(cx == XPCJSRuntime::Get()->GetJSContextStack()->Peek()); michael@0: michael@0: if (!mXPC) michael@0: return; michael@0: michael@0: mXPCContext = XPCContext::GetXPCContext(mJSContext); michael@0: mPrevCallerLanguage = mXPCContext->SetCallingLangType(mCallerLanguage); michael@0: michael@0: // hook into call context chain. michael@0: mPrevCallContext = XPCJSRuntime::Get()->SetCallContext(this); michael@0: michael@0: mState = HAVE_CONTEXT; michael@0: michael@0: if (!obj) michael@0: return; michael@0: michael@0: mMethodIndex = 0xDEAD; michael@0: michael@0: mState = HAVE_OBJECT; michael@0: michael@0: mTearOff = nullptr; michael@0: michael@0: // If the object is a security wrapper, GetWrappedNativeOfJSObject can't michael@0: // handle it. Do special handling here to make cross-origin Xrays work. michael@0: JSObject *unwrapped = js::CheckedUnwrap(obj, /* stopAtOuter = */ false); michael@0: if (!unwrapped) { michael@0: mWrapper = UnwrapThisIfAllowed(obj, funobj, argc); michael@0: if (!mWrapper) { michael@0: JS_ReportError(mJSContext, "Permission denied to call method on |this|"); michael@0: mState = INIT_FAILED; michael@0: return; michael@0: } michael@0: } else { michael@0: const js::Class *clasp = js::GetObjectClass(unwrapped); michael@0: if (IS_WN_CLASS(clasp)) { michael@0: mWrapper = XPCWrappedNative::Get(unwrapped); michael@0: } else if (IS_TEAROFF_CLASS(clasp)) { michael@0: mTearOff = (XPCWrappedNativeTearOff*)js::GetObjectPrivate(unwrapped); michael@0: mWrapper = XPCWrappedNative::Get(js::GetObjectParent(unwrapped)); michael@0: } michael@0: } michael@0: if (mWrapper) { michael@0: mFlattenedJSObject = mWrapper->GetFlatJSObject(); michael@0: michael@0: if (mTearOff) michael@0: mScriptableInfo = nullptr; michael@0: else michael@0: mScriptableInfo = mWrapper->GetScriptableInfo(); michael@0: } else { michael@0: MOZ_ASSERT(!mFlattenedJSObject, "What object do we have?"); michael@0: } michael@0: michael@0: if (!JSID_IS_VOID(name)) michael@0: SetName(name); michael@0: michael@0: if (argc != NO_ARGS) michael@0: SetArgsAndResultPtr(argc, argv, rval); michael@0: michael@0: CHECK_STATE(HAVE_OBJECT); michael@0: } michael@0: michael@0: // static michael@0: JSContext * michael@0: XPCCallContext::GetDefaultJSContext() michael@0: { michael@0: // This is slightly questionable. If called without an explicit michael@0: // JSContext (generally a call to a wrappedJS) we will use the JSContext michael@0: // on the top of the JSContext stack - if there is one - *before* michael@0: // falling back on the safe JSContext. michael@0: // This is good AND bad because it makes calls from JS -> native -> JS michael@0: // have JS stack 'continuity' for purposes of stack traces etc. michael@0: // Note: this *is* what the pre-XPCCallContext xpconnect did too. michael@0: michael@0: XPCJSContextStack* stack = XPCJSRuntime::Get()->GetJSContextStack(); michael@0: JSContext *topJSContext = stack->Peek(); michael@0: michael@0: return topJSContext ? topJSContext : stack->GetSafeJSContext(); michael@0: } michael@0: michael@0: void michael@0: XPCCallContext::SetName(jsid name) michael@0: { michael@0: CHECK_STATE(HAVE_OBJECT); michael@0: michael@0: mName = name; michael@0: michael@0: if (mTearOff) { michael@0: mSet = nullptr; michael@0: mInterface = mTearOff->GetInterface(); michael@0: mMember = mInterface->FindMember(mName); michael@0: mStaticMemberIsLocal = true; michael@0: if (mMember && !mMember->IsConstant()) michael@0: mMethodIndex = mMember->GetIndex(); michael@0: } else { michael@0: mSet = mWrapper ? mWrapper->GetSet() : nullptr; michael@0: michael@0: if (mSet && michael@0: mSet->FindMember(mName, &mMember, &mInterface, michael@0: mWrapper->HasProto() ? michael@0: mWrapper->GetProto()->GetSet() : michael@0: nullptr, michael@0: &mStaticMemberIsLocal)) { michael@0: if (mMember && !mMember->IsConstant()) michael@0: mMethodIndex = mMember->GetIndex(); michael@0: } else { michael@0: mMember = nullptr; michael@0: mInterface = nullptr; michael@0: mStaticMemberIsLocal = false; michael@0: } michael@0: } michael@0: michael@0: mState = HAVE_NAME; michael@0: } michael@0: michael@0: void michael@0: XPCCallContext::SetCallInfo(XPCNativeInterface* iface, XPCNativeMember* member, michael@0: bool isSetter) michael@0: { michael@0: CHECK_STATE(HAVE_CONTEXT); michael@0: michael@0: // We are going straight to the method info and need not do a lookup michael@0: // by id. michael@0: michael@0: // don't be tricked if method is called with wrong 'this' michael@0: if (mTearOff && mTearOff->GetInterface() != iface) michael@0: mTearOff = nullptr; michael@0: michael@0: mSet = nullptr; michael@0: mInterface = iface; michael@0: mMember = member; michael@0: mMethodIndex = mMember->GetIndex() + (isSetter ? 1 : 0); michael@0: mName = mMember->GetName(); michael@0: michael@0: if (mState < HAVE_NAME) michael@0: mState = HAVE_NAME; michael@0: } michael@0: michael@0: void michael@0: XPCCallContext::SetArgsAndResultPtr(unsigned argc, michael@0: jsval *argv, michael@0: jsval *rval) michael@0: { michael@0: CHECK_STATE(HAVE_OBJECT); michael@0: michael@0: if (mState < HAVE_NAME) { michael@0: mSet = nullptr; michael@0: mInterface = nullptr; michael@0: mMember = nullptr; michael@0: mStaticMemberIsLocal = false; michael@0: } michael@0: michael@0: mArgc = argc; michael@0: mArgv = argv; michael@0: mRetVal = rval; michael@0: michael@0: mState = HAVE_ARGS; michael@0: } michael@0: michael@0: nsresult michael@0: XPCCallContext::CanCallNow() michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (!HasInterfaceAndMember()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: if (mState < HAVE_ARGS) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: if (!mTearOff) { michael@0: mTearOff = mWrapper->FindTearOff(mInterface, false, &rv); michael@0: if (!mTearOff || mTearOff->GetInterface() != mInterface) { michael@0: mTearOff = nullptr; michael@0: return NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: // Refresh in case FindTearOff extended the set michael@0: mSet = mWrapper->GetSet(); michael@0: michael@0: mState = READY_TO_CALL; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: XPCCallContext::SystemIsBeingShutDown() michael@0: { michael@0: // XXX This is pretty questionable since the per thread cleanup stuff michael@0: // can be making this call on one thread for call contexts on another michael@0: // thread. michael@0: NS_WARNING("Shutting Down XPConnect even through there is a live XPCCallContext"); michael@0: mXPCContext = nullptr; michael@0: mState = SYSTEM_SHUTDOWN; michael@0: if (mPrevCallContext) michael@0: mPrevCallContext->SystemIsBeingShutDown(); michael@0: } michael@0: michael@0: XPCCallContext::~XPCCallContext() michael@0: { michael@0: if (mXPCContext) { michael@0: mXPCContext->SetCallingLangType(mPrevCallerLanguage); michael@0: michael@0: DebugOnly old = XPCJSRuntime::Get()->SetCallContext(mPrevCallContext); michael@0: MOZ_ASSERT(old == this, "bad pop from per thread data"); michael@0: } michael@0: } michael@0: michael@0: /* readonly attribute nsISupports Callee; */ michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetCallee(nsISupports * *aCallee) michael@0: { michael@0: nsCOMPtr rval = mWrapper ? mWrapper->GetIdentityObject() : nullptr; michael@0: rval.forget(aCallee); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute uint16_t CalleeMethodIndex; */ michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetCalleeMethodIndex(uint16_t *aCalleeMethodIndex) michael@0: { michael@0: *aCalleeMethodIndex = mMethodIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute nsIXPConnectWrappedNative CalleeWrapper; */ michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetCalleeWrapper(nsIXPConnectWrappedNative * *aCalleeWrapper) michael@0: { michael@0: nsCOMPtr rval = mWrapper; michael@0: rval.forget(aCalleeWrapper); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute XPCNativeInterface CalleeInterface; */ michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetCalleeInterface(nsIInterfaceInfo * *aCalleeInterface) michael@0: { michael@0: nsCOMPtr rval = mInterface->GetInterfaceInfo(); michael@0: rval.forget(aCalleeInterface); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute nsIClassInfo CalleeClassInfo; */ michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetCalleeClassInfo(nsIClassInfo * *aCalleeClassInfo) michael@0: { michael@0: nsCOMPtr rval = mWrapper ? mWrapper->GetClassInfo() : nullptr; michael@0: rval.forget(aCalleeClassInfo); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute JSContextPtr JSContext; */ michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetJSContext(JSContext * *aJSContext) michael@0: { michael@0: JS_AbortIfWrongThread(JS_GetRuntime(mJSContext)); michael@0: *aJSContext = mJSContext; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute uint32_t Argc; */ michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetArgc(uint32_t *aArgc) michael@0: { michael@0: *aArgc = (uint32_t) mArgc; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute JSValPtr ArgvPtr; */ michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetArgvPtr(jsval * *aArgvPtr) michael@0: { michael@0: *aArgvPtr = mArgv; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetPreviousCallContext(nsAXPCNativeCallContext **aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = GetPrevCallContext(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XPCCallContext::GetLanguage(uint16_t *aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = GetCallerLanguage(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: XPCWrappedNative* michael@0: XPCCallContext::UnwrapThisIfAllowed(HandleObject obj, HandleObject fun, unsigned argc) michael@0: { michael@0: // We should only get here for objects that aren't safe to unwrap. michael@0: MOZ_ASSERT(!js::CheckedUnwrap(obj)); michael@0: MOZ_ASSERT(js::IsObjectInContextCompartment(obj, mJSContext)); michael@0: michael@0: // We can't do anything here without a function. michael@0: if (!fun) michael@0: return nullptr; michael@0: michael@0: // Determine if we're allowed to unwrap the security wrapper to invoke the michael@0: // method. michael@0: // michael@0: // We have the Interface and Member that this corresponds to, but michael@0: // unfortunately our access checks are based on the object class name and michael@0: // property name. So we cheat a little bit here - we verify that the object michael@0: // does indeed implement the method's Interface, and then just check that we michael@0: // can successfully access property with method's name from the object. michael@0: michael@0: // First, get the XPCWN out of the underlying object. We should have a wrapper michael@0: // here, potentially an outer window proxy, and then an XPCWN. michael@0: MOZ_ASSERT(js::IsWrapper(obj)); michael@0: RootedObject unwrapped(mJSContext, js::UncheckedUnwrap(obj, /* stopAtOuter = */ false)); michael@0: #ifdef DEBUG michael@0: JS::Rooted wrappedObj(mJSContext, js::Wrapper::wrappedObject(obj)); michael@0: MOZ_ASSERT(unwrapped == JS_ObjectToInnerObject(mJSContext, wrappedObj)); michael@0: #endif michael@0: michael@0: // Make sure we have an XPCWN, and grab it. michael@0: if (!IS_WN_REFLECTOR(unwrapped)) michael@0: return nullptr; michael@0: XPCWrappedNative *wn = XPCWrappedNative::Get(unwrapped); michael@0: michael@0: // Next, get the call info off the function object. michael@0: XPCNativeInterface *interface; michael@0: XPCNativeMember *member; michael@0: XPCNativeMember::GetCallInfo(fun, &interface, &member); michael@0: michael@0: // To be extra safe, make sure that the underlying native implements the michael@0: // interface before unwrapping. Even if we didn't check this, we'd still michael@0: // theoretically fail during tearoff lookup for mismatched methods. michael@0: if (!wn->HasInterfaceNoQI(*interface->GetIID())) michael@0: return nullptr; michael@0: michael@0: // See if the access is permitted. michael@0: // michael@0: // NB: This calculation of SET vs GET is a bit wonky, but that's what michael@0: // XPC_WN_GetterSetter does. michael@0: bool set = argc && argc != NO_ARGS && member->IsWritableAttribute(); michael@0: js::Wrapper::Action act = set ? js::Wrapper::SET : js::Wrapper::GET; michael@0: js::Wrapper *handler = js::Wrapper::wrapperHandler(obj); michael@0: bool ignored; michael@0: JS::Rooted id(mJSContext, member->GetName()); michael@0: if (!handler->enter(mJSContext, obj, id, act, &ignored)) michael@0: return nullptr; michael@0: michael@0: // Ok, this call is safe. michael@0: return wn; michael@0: } michael@0: