dom/xslt/xslt/txExecutionState.cpp
author Andrea Marchesini <amarchesini@mozilla.com>
Tue, 20 Sep 2016 14:02:33 +0200
changeset 314619 31ea105ca2d9b3088d91628ae77603c469d525da
parent 288551 fe5bc1369d3efe3501995a8a061a8862bcb10723
child 324709 667c1a30679730fbf513e310b2768bc8eea5cc51
permissions -rw-r--r--
Bug 1301254 - nsINode::GetBaseURI() should be fallible, r=smaug

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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"

const int32_t txExecutionState::kMaxRecursionDepth = 20000;

nsresult
txLoadedDocumentsHash::init(txXPathNode* aSourceDocument)
{
    mSourceDocument = aSourceDocument;

    nsAutoString baseURI;
    nsresult rv = txXPathNodeUtils::getBaseURI(*mSourceDocument, baseURI);
    if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
    }

    PutEntry(baseURI)->mDocument = mSourceDocument;
    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.forget();
	    }
       }
    }
}

txExecutionState::txExecutionState(txStylesheet* aStylesheet,
                                   bool aDisableLoads)
    : mOutputHandler(nullptr),
      mResultHandler(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();
    }

    txStackIterator paramIter(&mParamStack);
    while (paramIter.hasNext()) {
        delete (txVariableMap*)paramIter.next();
    }

    delete mInitialEvalContext;
}

nsresult
txExecutionState::init(const txXPathNode& aNode,
                       txOwningExpandedNameMap<txIGlobalParameter>* 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(txXPathNodeUtils::getOwnerDocument(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(NS_LITERAL_STRING("Error"), nullptr);

    // Initiate first instruction. This has to be done last since findTemplate
    // might use us.
    txStylesheet::ImportFrame* frame = 0;
    txExpandedName nullName;
    txInstruction* templ = mStylesheet->findTemplate(aNode, nullName,
                                                     this, nullptr, &frame);
    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);
}



nsresult
txExecutionState::getVariable(int32_t aNamespace, nsIAtom* 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;

        NS_ENSURE_SUCCESS(rv, rv);
    }
    else {
        nsAutoPtr<txRtfHandler> rtfHandler(new txRtfHandler);
        NS_ENSURE_TRUE(rtfHandler, NS_ERROR_OUT_OF_MEMORY);

        rv = pushResultHandler(rtfHandler);
        NS_ENSURE_SUCCESS(rv, rv);
        
        rtfHandler.forget();

        txInstruction* prevInstr = mNextInstruction;
        // set return to nullptr to stop execution
        mNextInstruction = nullptr;
        rv = runTemplate(var->mFirstInstruction);
        NS_ENSURE_SUCCESS(rv, rv);

        pushTemplateRule(nullptr, txExpandedName(), nullptr);
        rv = txXSLTProcessor::execute(*this);
        NS_ENSURE_SUCCESS(rv, rv);

        popTemplateRule();

        mNextInstruction = prevInstr;
        rtfHandler = (txRtfHandler*)popResultHandler();
        rv = rtfHandler->getAsRTF(&aResult);
        NS_ENSURE_SUCCESS(rv, 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;
}

bool
txExecutionState::isStripSpaceAllowed(const txXPathNode& aNode)
{
    return mStylesheet->isStripSpaceAllowed(aNode, this);
}

void*
txExecutionState::getPrivateContext()
{
    return this;
}

txResultRecycler*
txExecutionState::recycler()
{
    return mRecycler;
}

void
txExecutionState::receiveError(const nsAString& aMsg, nsresult aRes)
{
    // XXX implement me
}

nsresult
txExecutionState::pushEvalContext(txIEvalContext* aContext)
{
    nsresult rv = mEvalContextStack.push(mEvalContext);
    NS_ENSURE_SUCCESS(rv, rv);
    
    mEvalContext = aContext;
    
    return NS_OK;
}

txIEvalContext*
txExecutionState::popEvalContext()
{
    txIEvalContext* prev = mEvalContext;
    mEvalContext = (txIEvalContext*)mEvalContextStack.pop();
    
    return prev;
}

nsresult
txExecutionState::pushBool(bool aBool)
{
    return mBoolStack.AppendElement(aBool) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

bool
txExecutionState::popBool()
{
    NS_ASSERTION(mBoolStack.Length(), "popping from empty stack");
    uint32_t last = mBoolStack.Length() - 1;
    NS_ENSURE_TRUE(last != (uint32_t)-1, false);

    bool res = mBoolStack.ElementAt(last);
    mBoolStack.RemoveElementAt(last);

    return res;
}

nsresult
txExecutionState::pushResultHandler(txAXMLEventHandler* aHandler)
{
    nsresult rv = mResultHandlerStack.push(mResultHandler);
    NS_ENSURE_SUCCESS(rv, rv);
    
    mResultHandler = aHandler;

    return NS_OK;
}

txAXMLEventHandler*
txExecutionState::popResultHandler()
{
    txAXMLEventHandler* oldHandler = mResultHandler;
    mResultHandler = (txAXMLEventHandler*)mResultHandlerStack.pop();

    return oldHandler;
}

void
txExecutionState::pushTemplateRule(txStylesheet::ImportFrame* aFrame,
                                   const txExpandedName& aMode,
                                   txVariableMap* aParams)
{
    TemplateRule* rule = mTemplateRules.AppendElement();
    rule->mFrame = aFrame;
    rule->mModeNsId = aMode.mNamespaceID;
    rule->mModeLocalName = aMode.mLocalName;
    rule->mParams = aParams;
}

void
txExecutionState::popTemplateRule()
{
    NS_PRECONDITION(!mTemplateRules.IsEmpty(), "No rules to pop");
    mTemplateRules.RemoveElementAt(mTemplateRules.Length() - 1);
}

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, 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(NS_LITERAL_STRING("Couldn't load document '") +
                         aUri + NS_LITERAL_STRING("': ") + errMsg,
                         entry->mLoadResult);
        }
    }

    return entry->mDocument;
}

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()
{
    NS_PRECONDITION(!mTemplateRules.IsEmpty(), "No current rule!");
    return &mTemplateRules[mTemplateRules.Length() - 1];
}

txInstruction*
txExecutionState::getNextInstruction()
{
    txInstruction* instr = mNextInstruction;
    if (instr) {
        mNextInstruction = instr->mNext;
    }
    
    return instr;
}

nsresult
txExecutionState::runTemplate(txInstruction* aTemplate)
{
    NS_ENSURE_TRUE(++mRecursionDepth < kMaxRecursionDepth,
                   NS_ERROR_XSLT_BAD_RECURSION);

    nsresult rv = mLocalVarsStack.push(mLocalVariables);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mReturnStack.push(mNextInstruction);
    NS_ENSURE_SUCCESS(rv, rv);
    
    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);
}

nsresult
txExecutionState::pushParamMap(txVariableMap* aParams)
{
    nsresult rv = mParamStack.push(mTemplateParams);
    NS_ENSURE_SUCCESS(rv, rv);

    mTemplateParams.forget();
    mTemplateParams = aParams;
    
    return NS_OK;
}

txVariableMap*
txExecutionState::popParamMap()
{
    txVariableMap* oldParams = mTemplateParams.forget();
    mTemplateParams = (txVariableMap*)mParamStack.pop();

    return oldParams;
}