michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 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: #include "nsAutoPtr.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsDependentString.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIInterfaceInfoManager.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "txExpr.h" michael@0: #include "txIFunctionEvaluationContext.h" michael@0: #include "txIXPathContext.h" michael@0: #include "txNodeSetAdaptor.h" michael@0: #include "txXPathTreeWalker.h" michael@0: #include "xptcall.h" michael@0: #include "txXPathObjectAdaptor.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsIClassInfo.h" michael@0: #include "nsIInterfaceInfo.h" michael@0: michael@0: NS_IMPL_ISUPPORTS(txXPathObjectAdaptor, txIXPathObject) michael@0: michael@0: class txFunctionEvaluationContext MOZ_FINAL : public txIFunctionEvaluationContext michael@0: { michael@0: public: michael@0: txFunctionEvaluationContext(txIEvalContext *aContext, nsISupports *aState); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_TXIFUNCTIONEVALUATIONCONTEXT michael@0: michael@0: void ClearContext() michael@0: { michael@0: mContext = nullptr; michael@0: } michael@0: michael@0: private: michael@0: txIEvalContext *mContext; michael@0: nsCOMPtr mState; michael@0: }; michael@0: michael@0: txFunctionEvaluationContext::txFunctionEvaluationContext(txIEvalContext *aContext, michael@0: nsISupports *aState) michael@0: : mContext(aContext), michael@0: mState(aState) michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(txFunctionEvaluationContext, txIFunctionEvaluationContext) michael@0: michael@0: NS_IMETHODIMP michael@0: txFunctionEvaluationContext::GetPosition(uint32_t *aPosition) michael@0: { michael@0: NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); michael@0: michael@0: *aPosition = mContext->position(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: txFunctionEvaluationContext::GetSize(uint32_t *aSize) michael@0: { michael@0: NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); michael@0: michael@0: *aSize = mContext->size(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: txFunctionEvaluationContext::GetContextNode(nsIDOMNode **aNode) michael@0: { michael@0: NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); michael@0: michael@0: return txXPathNativeNode::getNode(mContext->getContextNode(), aNode); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: txFunctionEvaluationContext::GetState(nsISupports **aState) michael@0: { michael@0: NS_IF_ADDREF(*aState = mState); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: enum txArgumentType { michael@0: eBOOLEAN = nsXPTType::T_BOOL, michael@0: eNUMBER = nsXPTType::T_DOUBLE, michael@0: eSTRING = nsXPTType::T_DOMSTRING, michael@0: eNODESET, michael@0: eCONTEXT, michael@0: eOBJECT, michael@0: eUNKNOWN michael@0: }; michael@0: michael@0: class txXPCOMExtensionFunctionCall : public FunctionCall michael@0: { michael@0: public: michael@0: txXPCOMExtensionFunctionCall(nsISupports *aHelper, const nsIID &aIID, michael@0: uint16_t aMethodIndex, michael@0: #ifdef TX_TO_STRING michael@0: nsIAtom *aName, michael@0: #endif michael@0: nsISupports *aState); michael@0: michael@0: TX_DECL_FUNCTION michael@0: michael@0: private: michael@0: txArgumentType GetParamType(const nsXPTParamInfo &aParam, michael@0: nsIInterfaceInfo *aInfo); michael@0: michael@0: nsCOMPtr mHelper; michael@0: nsIID mIID; michael@0: uint16_t mMethodIndex; michael@0: #ifdef TX_TO_STRING michael@0: nsCOMPtr mName; michael@0: #endif michael@0: nsCOMPtr mState; michael@0: }; michael@0: michael@0: txXPCOMExtensionFunctionCall::txXPCOMExtensionFunctionCall(nsISupports *aHelper, michael@0: const nsIID &aIID, michael@0: uint16_t aMethodIndex, michael@0: #ifdef TX_TO_STRING michael@0: nsIAtom *aName, michael@0: #endif michael@0: nsISupports *aState) michael@0: : mHelper(aHelper), michael@0: mIID(aIID), michael@0: mMethodIndex(aMethodIndex), michael@0: #ifdef TX_TO_STRING michael@0: mName(aName), michael@0: #endif michael@0: mState(aState) michael@0: { michael@0: } michael@0: michael@0: class txInterfacesArrayHolder michael@0: { michael@0: public: michael@0: txInterfacesArrayHolder(nsIID **aArray, uint32_t aCount) : mArray(aArray), michael@0: mCount(aCount) michael@0: { michael@0: } michael@0: ~txInterfacesArrayHolder() michael@0: { michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mArray); michael@0: } michael@0: michael@0: private: michael@0: nsIID **mArray; michael@0: uint32_t mCount; michael@0: }; michael@0: michael@0: static nsresult michael@0: LookupFunction(const char *aContractID, nsIAtom* aName, nsIID &aIID, michael@0: uint16_t &aMethodIndex, nsISupports **aHelper) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr helper = do_GetService(aContractID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr classInfo = do_QueryInterface(helper, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr iim = michael@0: do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(iim, NS_ERROR_FAILURE); michael@0: michael@0: nsIID** iidArray = nullptr; michael@0: uint32_t iidCount = 0; michael@0: rv = classInfo->GetInterfaces(&iidCount, &iidArray); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: txInterfacesArrayHolder holder(iidArray, iidCount); michael@0: michael@0: // Remove any minus signs and uppercase the following letter (so michael@0: // foo-bar becomes fooBar). Note that if there are any names that already michael@0: // have uppercase letters they might cause false matches (both fooBar and michael@0: // foo-bar matching fooBar). michael@0: const char16_t *name = aName->GetUTF16String(); michael@0: nsAutoCString methodName; michael@0: char16_t letter; michael@0: bool upperNext = false; michael@0: while ((letter = *name)) { michael@0: if (letter == '-') { michael@0: upperNext = true; michael@0: } michael@0: else { michael@0: MOZ_ASSERT(nsCRT::IsAscii(letter), michael@0: "invalid static_cast coming up"); michael@0: methodName.Append(upperNext ? michael@0: nsCRT::ToUpper(static_cast(letter)) : michael@0: letter); michael@0: upperNext = false; michael@0: } michael@0: ++name; michael@0: } michael@0: michael@0: uint32_t i; michael@0: for (i = 0; i < iidCount; ++i) { michael@0: nsIID *iid = iidArray[i]; michael@0: michael@0: nsCOMPtr info; michael@0: rv = iim->GetInfoForIID(iid, getter_AddRefs(info)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint16_t methodIndex; michael@0: const nsXPTMethodInfo *methodInfo; michael@0: rv = info->GetMethodInfoForName(methodName.get(), &methodIndex, michael@0: &methodInfo); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Exclude notxpcom and hidden. Also check that we have at least a michael@0: // return value (the xpidl compiler ensures that that return value michael@0: // is the last argument). michael@0: uint8_t paramCount = methodInfo->GetParamCount(); michael@0: if (methodInfo->IsNotXPCOM() || methodInfo->IsHidden() || michael@0: paramCount == 0 || michael@0: !methodInfo->GetParam(paramCount - 1).IsRetval()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: aIID = *iid; michael@0: aMethodIndex = methodIndex; michael@0: return helper->QueryInterface(aIID, (void**)aHelper); michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_XPATH_UNKNOWN_FUNCTION; michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: TX_ResolveFunctionCallXPCOM(const nsCString &aContractID, int32_t aNamespaceID, michael@0: nsIAtom* aName, nsISupports *aState, michael@0: FunctionCall **aFunction) michael@0: { michael@0: nsIID iid; michael@0: uint16_t methodIndex = 0; michael@0: nsCOMPtr helper; michael@0: michael@0: nsresult rv = LookupFunction(aContractID.get(), aName, iid, methodIndex, michael@0: getter_AddRefs(helper)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!aFunction) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aFunction = new txXPCOMExtensionFunctionCall(helper, iid, methodIndex, michael@0: #ifdef TX_TO_STRING michael@0: aName, michael@0: #endif michael@0: aState); michael@0: michael@0: return *aFunction ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: txArgumentType michael@0: txXPCOMExtensionFunctionCall::GetParamType(const nsXPTParamInfo &aParam, michael@0: nsIInterfaceInfo *aInfo) michael@0: { michael@0: uint8_t tag = aParam.GetType().TagPart(); michael@0: switch (tag) { michael@0: case nsXPTType::T_BOOL: michael@0: case nsXPTType::T_DOUBLE: michael@0: case nsXPTType::T_DOMSTRING: michael@0: { michael@0: return txArgumentType(tag); michael@0: } michael@0: case nsXPTType::T_INTERFACE: michael@0: case nsXPTType::T_INTERFACE_IS: michael@0: { michael@0: nsIID iid; michael@0: aInfo->GetIIDForParamNoAlloc(mMethodIndex, &aParam, &iid); michael@0: if (iid.Equals(NS_GET_IID(txINodeSet))) { michael@0: return eNODESET; michael@0: } michael@0: if (iid.Equals(NS_GET_IID(txIFunctionEvaluationContext))) { michael@0: return eCONTEXT; michael@0: } michael@0: if (iid.Equals(NS_GET_IID(txIXPathObject))) { michael@0: return eOBJECT; michael@0: } michael@0: } michael@0: // FALLTHROUGH michael@0: default: michael@0: { michael@0: // XXX Error! michael@0: return eUNKNOWN; michael@0: } michael@0: } michael@0: } michael@0: michael@0: class txParamArrayHolder michael@0: { michael@0: public: michael@0: txParamArrayHolder() michael@0: : mCount(0) michael@0: { michael@0: } michael@0: ~txParamArrayHolder(); michael@0: michael@0: bool Init(uint8_t aCount); michael@0: operator nsXPTCVariant*() const michael@0: { michael@0: return mArray; michael@0: } michael@0: michael@0: private: michael@0: nsAutoArrayPtr mArray; michael@0: uint8_t mCount; michael@0: }; michael@0: michael@0: txParamArrayHolder::~txParamArrayHolder() michael@0: { michael@0: uint8_t i; michael@0: for (i = 0; i < mCount; ++i) { michael@0: nsXPTCVariant &variant = mArray[i]; michael@0: if (variant.DoesValNeedCleanup()) { michael@0: if (variant.type.TagPart() == nsXPTType::T_DOMSTRING) michael@0: delete (nsAString*)variant.val.p; michael@0: else { michael@0: NS_ABORT_IF_FALSE(variant.type.TagPart() == nsXPTType::T_INTERFACE || michael@0: variant.type.TagPart() == nsXPTType::T_INTERFACE_IS, michael@0: "We only support cleanup of strings and interfaces " michael@0: "here, and this looks like neither!"); michael@0: static_cast(variant.val.p)->Release(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: txParamArrayHolder::Init(uint8_t aCount) michael@0: { michael@0: mCount = aCount; michael@0: mArray = new nsXPTCVariant[mCount]; michael@0: if (!mArray) { michael@0: return false; michael@0: } michael@0: michael@0: memset(mArray, 0, mCount * sizeof(nsXPTCVariant)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: txXPCOMExtensionFunctionCall::evaluate(txIEvalContext* aContext, michael@0: txAExprResult** aResult) michael@0: { michael@0: nsCOMPtr iim = michael@0: do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(iim, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr info; michael@0: nsresult rv = iim->GetInfoForIID(&mIID, getter_AddRefs(info)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: const nsXPTMethodInfo *methodInfo; michael@0: rv = info->GetMethodInfo(mMethodIndex, &methodInfo); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint8_t paramCount = methodInfo->GetParamCount(); michael@0: uint8_t inArgs = paramCount - 1; michael@0: michael@0: txParamArrayHolder invokeParams; michael@0: if (!invokeParams.Init(paramCount)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(0); michael@0: txArgumentType type = GetParamType(paramInfo, info); michael@0: if (type == eUNKNOWN) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: txFunctionEvaluationContext *context; michael@0: uint32_t paramStart = 0; michael@0: if (type == eCONTEXT) { michael@0: if (paramInfo.IsOut()) { michael@0: // We don't support out values. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Create context wrapper. michael@0: context = new txFunctionEvaluationContext(aContext, mState); michael@0: if (!context) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: nsXPTCVariant &invokeParam = invokeParams[0]; michael@0: invokeParam.type = paramInfo.GetType(); michael@0: invokeParam.SetValNeedsCleanup(); michael@0: NS_ADDREF((txIFunctionEvaluationContext*&)invokeParam.val.p = context); michael@0: michael@0: // Skip first argument, since it's the context. michael@0: paramStart = 1; michael@0: } michael@0: else { michael@0: context = nullptr; michael@0: } michael@0: michael@0: // XXX varargs michael@0: if (!requireParams(inArgs - paramStart, inArgs - paramStart, aContext)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint32_t i; michael@0: for (i = paramStart; i < inArgs; ++i) { michael@0: Expr* expr = mParams[i - paramStart]; michael@0: michael@0: const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(i); michael@0: txArgumentType type = GetParamType(paramInfo, info); michael@0: if (type == eUNKNOWN) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsXPTCVariant &invokeParam = invokeParams[i]; michael@0: if (paramInfo.IsOut()) { michael@0: // We don't support out values. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: invokeParam.type = paramInfo.GetType(); michael@0: switch (type) { michael@0: case eNODESET: michael@0: { michael@0: nsRefPtr nodes; michael@0: rv = evaluateToNodeSet(expr, aContext, getter_AddRefs(nodes)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: txNodeSetAdaptor *adaptor = new txNodeSetAdaptor(nodes); michael@0: if (!adaptor) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: nsCOMPtr nodeSet = adaptor; michael@0: rv = adaptor->Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: invokeParam.SetValNeedsCleanup(); michael@0: nodeSet.swap((txINodeSet*&)invokeParam.val.p); michael@0: break; michael@0: } michael@0: case eBOOLEAN: michael@0: { michael@0: rv = expr->evaluateToBool(aContext, invokeParam.val.b); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: break; michael@0: } michael@0: case eNUMBER: michael@0: { michael@0: double dbl; michael@0: rv = evaluateToNumber(mParams[0], aContext, &dbl); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: invokeParam.val.d = dbl; michael@0: break; michael@0: } michael@0: case eSTRING: michael@0: { michael@0: nsString *value = new nsString(); michael@0: if (!value) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: rv = expr->evaluateToString(aContext, *value); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: invokeParam.SetValNeedsCleanup(); michael@0: invokeParam.val.p = value; michael@0: break; michael@0: } michael@0: case eOBJECT: michael@0: { michael@0: nsRefPtr exprRes; michael@0: rv = expr->evaluate(aContext, getter_AddRefs(exprRes)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr adaptor = michael@0: new txXPathObjectAdaptor(exprRes); michael@0: if (!adaptor) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: invokeParam.SetValNeedsCleanup(); michael@0: adaptor.swap((txIXPathObject*&)invokeParam.val.p); michael@0: break; michael@0: } michael@0: case eCONTEXT: michael@0: case eUNKNOWN: michael@0: { michael@0: // We only support passing the context as the *first* argument. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: const nsXPTParamInfo &returnInfo = methodInfo->GetParam(inArgs); michael@0: txArgumentType returnType = GetParamType(returnInfo, info); michael@0: if (returnType == eUNKNOWN) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsXPTCVariant &returnParam = invokeParams[inArgs]; michael@0: returnParam.type = returnInfo.GetType(); michael@0: if (returnType == eSTRING) { michael@0: nsString *value = new nsString(); michael@0: if (!value) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: returnParam.SetValNeedsCleanup(); michael@0: returnParam.val.p = value; michael@0: } michael@0: else { michael@0: returnParam.SetIndirect(); michael@0: if (returnType == eNODESET || returnType == eOBJECT) { michael@0: returnParam.SetValNeedsCleanup(); michael@0: } michael@0: } michael@0: michael@0: rv = NS_InvokeByIndex(mHelper, mMethodIndex, paramCount, invokeParams); michael@0: michael@0: // In case someone is holding on to the txFunctionEvaluationContext which michael@0: // could thus stay alive longer than this function. michael@0: if (context) { michael@0: context->ClearContext(); michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: switch (returnType) { michael@0: case eNODESET: michael@0: { michael@0: txINodeSet* nodeSet = static_cast(returnParam.val.p); michael@0: nsCOMPtr object = do_QueryInterface(nodeSet, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ADDREF(*aResult = object->GetResult()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: case eBOOLEAN: michael@0: { michael@0: aContext->recycler()->getBoolResult(returnParam.val.b, aResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: case eNUMBER: michael@0: { michael@0: return aContext->recycler()->getNumberResult(returnParam.val.d, michael@0: aResult); michael@0: } michael@0: case eSTRING: michael@0: { michael@0: nsString *returned = static_cast michael@0: (returnParam.val.p); michael@0: return aContext->recycler()->getStringResult(*returned, aResult); michael@0: } michael@0: case eOBJECT: michael@0: { michael@0: txIXPathObject *object = michael@0: static_cast(returnParam.val.p); michael@0: michael@0: NS_ADDREF(*aResult = object->GetResult()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: default: michael@0: { michael@0: // Huh? michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: Expr::ResultType michael@0: txXPCOMExtensionFunctionCall::getReturnType() michael@0: { michael@0: // It doesn't really matter what we return here, but it might michael@0: // be a good idea to try to keep this as unoptimizable as possible michael@0: return ANY_RESULT; michael@0: } michael@0: michael@0: bool michael@0: txXPCOMExtensionFunctionCall::isSensitiveTo(ContextSensitivity aContext) michael@0: { michael@0: // It doesn't really matter what we return here, but it might michael@0: // be a good idea to try to keep this as unoptimizable as possible michael@0: return true; michael@0: } michael@0: michael@0: #ifdef TX_TO_STRING michael@0: nsresult michael@0: txXPCOMExtensionFunctionCall::getNameAtom(nsIAtom** aAtom) michael@0: { michael@0: NS_ADDREF(*aAtom = mName); michael@0: michael@0: return NS_OK; michael@0: } michael@0: #endif