/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "txExecutionState.h" #include "txSingleNodeContext.h" #include "txInstructions.h" #include "txStylesheet.h" #include "txVariableMap.h" #include "txRtfHandler.h" #include "txXSLTProcessor.h" #include "txLog.h" #include "txURIUtils.h" #include "txXMLParser.h" using mozilla::UniquePtr; using mozilla::Unused; using mozilla::WrapUnique; const int32_t txExecutionState::kMaxRecursionDepth = 20000; nsresult txLoadedDocumentsHash::init(const txXPathNode& aSource) { mSourceDocument = WrapUnique(txXPathNodeUtils::getOwnerDocument(aSource)); nsAutoString baseURI; nsresult rv = txXPathNodeUtils::getBaseURI(*mSourceDocument, baseURI); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Technically the hash holds documents, but we allow any node that we're // transforming from. In particular, the document() function uses this hash // and it can return the source document, but if we're transforming from a // document fragment (through // txMozillaXSLTProcessor::SetSourceContentModel/txMozillaXSLTProcessor::DoTransform) // or from another type of node (through // txMozillaXSLTProcessor::TransformToDocument or // txMozillaXSLTProcessor::TransformToFragment) it makes more sense to return // the real root of the source tree, which is the node where the transform // started. PutEntry(baseURI)->mDocument = WrapUnique( txXPathNativeNode::createXPathNode(txXPathNativeNode::getNode(aSource))); return NS_OK; } txLoadedDocumentsHash::~txLoadedDocumentsHash() { if (mSourceDocument) { nsAutoString baseURI; nsresult rv = txXPathNodeUtils::getBaseURI(*mSourceDocument, baseURI); if (NS_SUCCEEDED(rv)) { txLoadedDocumentEntry* entry = GetEntry(baseURI); if (entry) { delete entry->mDocument.release(); } } } } txExecutionState::txExecutionState(txStylesheet* aStylesheet, bool aDisableLoads) : mOutputHandler(nullptr), mResultHandler(nullptr), mOutputHandlerFactory(nullptr), mStylesheet(aStylesheet), mNextInstruction(nullptr), mLocalVariables(nullptr), mRecursionDepth(0), mEvalContext(nullptr), mInitialEvalContext(nullptr), mGlobalParams(nullptr), mKeyHash(aStylesheet->getKeyMap()), mDisableLoads(aDisableLoads) { MOZ_COUNT_CTOR(txExecutionState); } txExecutionState::~txExecutionState() { MOZ_COUNT_DTOR(txExecutionState); delete mResultHandler; delete mLocalVariables; if (mEvalContext != mInitialEvalContext) { delete mEvalContext; } txStackIterator varsIter(&mLocalVarsStack); while (varsIter.hasNext()) { delete (txVariableMap*)varsIter.next(); } txStackIterator contextIter(&mEvalContextStack); while (contextIter.hasNext()) { txIEvalContext* context = (txIEvalContext*)contextIter.next(); if (context != mInitialEvalContext) { delete context; } } txStackIterator handlerIter(&mResultHandlerStack); while (handlerIter.hasNext()) { delete (txAXMLEventHandler*)handlerIter.next(); } delete mInitialEvalContext; } nsresult txExecutionState::init( const txXPathNode& aNode, txOwningExpandedNameMap* aGlobalParams) { nsresult rv = NS_OK; mGlobalParams = aGlobalParams; // Set up initial context mEvalContext = new txSingleNodeContext(aNode, this); mInitialEvalContext = mEvalContext; // Set up output and result-handler txAXMLEventHandler* handler; rv = mOutputHandlerFactory->createHandlerWith(mStylesheet->getOutputFormat(), &handler); NS_ENSURE_SUCCESS(rv, rv); mOutputHandler = handler; mResultHandler = handler; mOutputHandler->startDocument(); // Set up loaded-documents-hash rv = mLoadedDocuments.init(aNode); NS_ENSURE_SUCCESS(rv, rv); // Init members rv = mKeyHash.init(); NS_ENSURE_SUCCESS(rv, rv); mRecycler = new txResultRecycler; // The actual value here doesn't really matter since noone should use this // value. But lets put something errorlike in just in case mGlobalVarPlaceholderValue = new StringResult(u"Error"_ns, nullptr); // Initiate first instruction. This has to be done last since findTemplate // might use us. txStylesheet::ImportFrame* frame = 0; txExpandedName nullName; txInstruction* templ; rv = mStylesheet->findTemplate(aNode, nullName, this, nullptr, &templ, &frame); NS_ENSURE_SUCCESS(rv, rv); pushTemplateRule(frame, nullName, nullptr); return runTemplate(templ); } nsresult txExecutionState::end(nsresult aResult) { NS_ASSERTION(NS_FAILED(aResult) || mTemplateRules.Length() == 1, "Didn't clean up template rules properly"); if (NS_SUCCEEDED(aResult)) { popTemplateRule(); } else if (!mOutputHandler) { return NS_OK; } return mOutputHandler->endDocument(aResult); } void txExecutionState::popAndDeleteEvalContextUntil(txIEvalContext* aContext) { auto ctx = popEvalContext(); while (ctx && ctx != aContext) { MOZ_RELEASE_ASSERT(ctx != mInitialEvalContext); delete ctx; ctx = popEvalContext(); } } nsresult txExecutionState::getVariable(int32_t aNamespace, nsAtom* aLName, txAExprResult*& aResult) { nsresult rv = NS_OK; txExpandedName name(aNamespace, aLName); // look for a local variable if (mLocalVariables) { mLocalVariables->getVariable(name, &aResult); if (aResult) { return NS_OK; } } // look for an evaluated global variable mGlobalVariableValues.getVariable(name, &aResult); if (aResult) { if (aResult == mGlobalVarPlaceholderValue) { // XXX ErrorReport: cyclic variable-value NS_RELEASE(aResult); return NS_ERROR_XSLT_BAD_RECURSION; } return NS_OK; } // Is there perchance a global variable not evaluated yet? txStylesheet::GlobalVariable* var = mStylesheet->getGlobalVariable(name); if (!var) { // XXX ErrorReport: variable doesn't exist in this scope return NS_ERROR_FAILURE; } NS_ASSERTION((var->mExpr && !var->mFirstInstruction) || (!var->mExpr && var->mFirstInstruction), "global variable should have either instruction or expression"); // Is this a stylesheet parameter that has a value? if (var->mIsParam && mGlobalParams) { txIGlobalParameter* param = mGlobalParams->get(name); if (param) { rv = param->getValue(&aResult); NS_ENSURE_SUCCESS(rv, rv); rv = mGlobalVariableValues.bindVariable(name, aResult); if (NS_FAILED(rv)) { NS_RELEASE(aResult); return rv; } return NS_OK; } } // Insert a placeholdervalue to protect against recursion rv = mGlobalVariableValues.bindVariable(name, mGlobalVarPlaceholderValue); NS_ENSURE_SUCCESS(rv, rv); // evaluate the global variable pushEvalContext(mInitialEvalContext); if (var->mExpr) { txVariableMap* oldVars = mLocalVariables; mLocalVariables = nullptr; rv = var->mExpr->evaluate(getEvalContext(), &aResult); mLocalVariables = oldVars; if (NS_FAILED(rv)) { popAndDeleteEvalContextUntil(mInitialEvalContext); return rv; } } else { pushResultHandler(new txRtfHandler); txInstruction* prevInstr = mNextInstruction; // set return to nullptr to stop execution mNextInstruction = nullptr; rv = runTemplate(var->mFirstInstruction.get()); if (NS_FAILED(rv)) { popAndDeleteEvalContextUntil(mInitialEvalContext); return rv; } pushTemplateRule(nullptr, txExpandedName(), nullptr); rv = txXSLTProcessor::execute(*this); if (NS_FAILED(rv)) { popAndDeleteEvalContextUntil(mInitialEvalContext); return rv; } popTemplateRule(); mNextInstruction = prevInstr; UniquePtr rtfHandler( static_cast(popResultHandler())); rv = rtfHandler->getAsRTF(&aResult); if (NS_FAILED(rv)) { popAndDeleteEvalContextUntil(mInitialEvalContext); return rv; } } popEvalContext(); // Remove the placeholder and insert the calculated value mGlobalVariableValues.removeVariable(name); rv = mGlobalVariableValues.bindVariable(name, aResult); if (NS_FAILED(rv)) { NS_RELEASE(aResult); return rv; } return NS_OK; } nsresult txExecutionState::isStripSpaceAllowed(const txXPathNode& aNode, bool& aAllowed) { return mStylesheet->isStripSpaceAllowed(aNode, this, aAllowed); } void* txExecutionState::getPrivateContext() { return this; } txResultRecycler* txExecutionState::recycler() { return mRecycler; } void txExecutionState::receiveError(const nsAString& aMsg, nsresult aRes) { // XXX implement me } void txExecutionState::pushEvalContext(txIEvalContext* aContext) { mEvalContextStack.push(mEvalContext); mEvalContext = aContext; } txIEvalContext* txExecutionState::popEvalContext() { txIEvalContext* prev = mEvalContext; mEvalContext = (txIEvalContext*)mEvalContextStack.pop(); return prev; } void txExecutionState::pushBool(bool aBool) { mBoolStack.AppendElement(aBool); } bool txExecutionState::popBool() { NS_ASSERTION(mBoolStack.Length(), "popping from empty stack"); return mBoolStack.IsEmpty() ? false : mBoolStack.PopLastElement(); } void txExecutionState::pushResultHandler(txAXMLEventHandler* aHandler) { mResultHandlerStack.push(mResultHandler); mResultHandler = aHandler; } txAXMLEventHandler* txExecutionState::popResultHandler() { txAXMLEventHandler* oldHandler = mResultHandler; mResultHandler = (txAXMLEventHandler*)mResultHandlerStack.pop(); return oldHandler; } void txExecutionState::pushTemplateRule(txStylesheet::ImportFrame* aFrame, const txExpandedName& aMode, txParameterMap* aParams) { TemplateRule* rule = mTemplateRules.AppendElement(); rule->mFrame = aFrame; rule->mModeNsId = aMode.mNamespaceID; rule->mModeLocalName = aMode.mLocalName; rule->mParams = aParams; } void txExecutionState::popTemplateRule() { MOZ_ASSERT(!mTemplateRules.IsEmpty(), "No rules to pop"); mTemplateRules.RemoveLastElement(); } txIEvalContext* txExecutionState::getEvalContext() { return mEvalContext; } const txXPathNode* txExecutionState::retrieveDocument(const nsAString& aUri) { NS_ASSERTION(!aUri.Contains(char16_t('#')), "Remove the fragment."); if (mDisableLoads) { return nullptr; } MOZ_LOG(txLog::xslt, mozilla::LogLevel::Debug, ("Retrieve Document %s", NS_LossyConvertUTF16toASCII(aUri).get())); // try to get already loaded document txLoadedDocumentEntry* entry = mLoadedDocuments.PutEntry(aUri); if (!entry) { return nullptr; } if (!entry->mDocument && !entry->LoadingFailed()) { // open URI nsAutoString errMsg; // XXX we should get the loader from the actual node // triggering the load, but this will do for the time being entry->mLoadResult = txParseDocumentFromURI(aUri, *mLoadedDocuments.mSourceDocument, errMsg, getter_Transfers(entry->mDocument)); if (entry->LoadingFailed()) { receiveError(u"Couldn't load document '"_ns + aUri + u"': "_ns + errMsg, entry->mLoadResult); } } return entry->mDocument.get(); } nsresult txExecutionState::getKeyNodes(const txExpandedName& aKeyName, const txXPathNode& aRoot, const nsAString& aKeyValue, bool aIndexIfNotFound, txNodeSet** aResult) { return mKeyHash.getKeyNodes(aKeyName, aRoot, aKeyValue, aIndexIfNotFound, *this, aResult); } txExecutionState::TemplateRule* txExecutionState::getCurrentTemplateRule() { MOZ_ASSERT(!mTemplateRules.IsEmpty(), "No current rule!"); return &mTemplateRules[mTemplateRules.Length() - 1]; } mozilla::Result txExecutionState::getNextInstruction() { if (mStopProcessing) { return mozilla::Err(NS_ERROR_FAILURE); } txInstruction* instr = mNextInstruction; if (instr) { mNextInstruction = instr->mNext.get(); } return instr; } nsresult txExecutionState::runTemplate(txInstruction* aTemplate) { NS_ENSURE_TRUE(++mRecursionDepth < kMaxRecursionDepth, NS_ERROR_XSLT_BAD_RECURSION); mLocalVarsStack.push(mLocalVariables); mReturnStack.push(mNextInstruction); mLocalVariables = nullptr; mNextInstruction = aTemplate; return NS_OK; } void txExecutionState::gotoInstruction(txInstruction* aNext) { mNextInstruction = aNext; } void txExecutionState::returnFromTemplate() { --mRecursionDepth; NS_ASSERTION(!mReturnStack.isEmpty() && !mLocalVarsStack.isEmpty(), "return or variable stack is empty"); delete mLocalVariables; mNextInstruction = (txInstruction*)mReturnStack.pop(); mLocalVariables = (txVariableMap*)mLocalVarsStack.pop(); } nsresult txExecutionState::bindVariable(const txExpandedName& aName, txAExprResult* aValue) { if (!mLocalVariables) { mLocalVariables = new txVariableMap; } return mLocalVariables->bindVariable(aName, aValue); } void txExecutionState::removeVariable(const txExpandedName& aName) { mLocalVariables->removeVariable(aName); } void txExecutionState::pushParamMap(txParameterMap* aParams) { mParamStack.AppendElement(mTemplateParams.forget()); mTemplateParams = aParams; } already_AddRefed txExecutionState::popParamMap() { RefPtr oldParams = std::move(mTemplateParams); mTemplateParams = mParamStack.PopLastElement(); return oldParams.forget(); }