dom/xslt/xpath/txNodeSet.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "txNodeSet.h"
     7 #include "txLog.h"
     8 #include "nsMemory.h"
     9 #include "txXPathTreeWalker.h"
    10 #include <algorithm>
    12 /**
    13  * Implementation of an XPath nodeset
    14  */
    16 #ifdef NS_BUILD_REFCNT_LOGGING
    17 #define LOG_CHUNK_MOVE(_start, _new_start, _count)            \
    18 {                                                             \
    19     txXPathNode *start = const_cast<txXPathNode*>(_start);    \
    20     while (start < _start + _count) {                         \
    21         NS_LogDtor(start, "txXPathNode", sizeof(*start));     \
    22         ++start;                                              \
    23     }                                                         \
    24     start = const_cast<txXPathNode*>(_new_start);             \
    25     while (start < _new_start + _count) {                     \
    26         NS_LogCtor(start, "txXPathNode", sizeof(*start));     \
    27         ++start;                                              \
    28     }                                                         \
    29 }
    30 #else
    31 #define LOG_CHUNK_MOVE(_start, _new_start, _count)
    32 #endif
    34 static const int32_t kTxNodeSetMinSize = 4;
    35 static const int32_t kTxNodeSetGrowFactor = 2;
    37 #define kForward   1
    38 #define kReversed -1
    40 txNodeSet::txNodeSet(txResultRecycler* aRecycler)
    41     : txAExprResult(aRecycler),
    42       mStart(nullptr),
    43       mEnd(nullptr),
    44       mStartBuffer(nullptr),
    45       mEndBuffer(nullptr),
    46       mDirection(kForward),
    47       mMarks(nullptr)
    48 {
    49 }
    51 txNodeSet::txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler)
    52     : txAExprResult(aRecycler),
    53       mStart(nullptr),
    54       mEnd(nullptr),
    55       mStartBuffer(nullptr),
    56       mEndBuffer(nullptr),
    57       mDirection(kForward),
    58       mMarks(nullptr)
    59 {
    60     if (!ensureGrowSize(1)) {
    61         return;
    62     }
    64     new(mStart) txXPathNode(aNode);
    65     ++mEnd;
    66 }
    68 txNodeSet::txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler)
    69     : txAExprResult(aRecycler),
    70       mStart(nullptr),
    71       mEnd(nullptr),
    72       mStartBuffer(nullptr),
    73       mEndBuffer(nullptr),
    74       mDirection(kForward),
    75       mMarks(nullptr)
    76 {
    77     append(aSource);
    78 }
    80 txNodeSet::~txNodeSet()
    81 {
    82     delete [] mMarks;
    84     if (mStartBuffer) {
    85         destroyElements(mStart, mEnd);
    87         nsMemory::Free(mStartBuffer);
    88     }
    89 }
    91 nsresult txNodeSet::add(const txXPathNode& aNode)
    92 {
    93     NS_ASSERTION(mDirection == kForward,
    94                  "only append(aNode) is supported on reversed nodesets");
    96     if (isEmpty()) {
    97         return append(aNode);
    98     }
   100     bool dupe;
   101     txXPathNode* pos = findPosition(aNode, mStart, mEnd, dupe);
   103     if (dupe) {
   104         return NS_OK;
   105     }
   107     // save pos, ensureGrowSize messes with the pointers
   108     int32_t moveSize = mEnd - pos;
   109     int32_t offset = pos - mStart;
   110     if (!ensureGrowSize(1)) {
   111         return NS_ERROR_OUT_OF_MEMORY;
   112     }
   113     // set pos to where it was
   114     pos = mStart + offset;
   116     if (moveSize > 0) {
   117         LOG_CHUNK_MOVE(pos, pos + 1, moveSize);
   118         memmove(pos + 1, pos, moveSize * sizeof(txXPathNode));
   119     }
   121     new(pos) txXPathNode(aNode);
   122     ++mEnd;
   124     return NS_OK;
   125 }
   127 nsresult txNodeSet::add(const txNodeSet& aNodes)
   128 {
   129     return add(aNodes, copyElements, nullptr);
   130 }
   132 nsresult txNodeSet::addAndTransfer(txNodeSet* aNodes)
   133 {
   134     // failure is out-of-memory, transfer didn't happen
   135     nsresult rv = add(*aNodes, transferElements, destroyElements);
   136     NS_ENSURE_SUCCESS(rv, rv);
   138 #ifdef TX_DONT_RECYCLE_BUFFER
   139     if (aNodes->mStartBuffer) {
   140         nsMemory::Free(aNodes->mStartBuffer);
   141         aNodes->mStartBuffer = aNodes->mEndBuffer = nullptr;
   142     }
   143 #endif
   144     aNodes->mStart = aNodes->mEnd = aNodes->mStartBuffer;
   146     return NS_OK;
   147 }
   149 /**
   150  * add(aNodeSet, aTransferOp)
   151  *
   152  * The code is optimized to make a minimum number of calls to
   153  * Node::compareDocumentPosition. The idea is this:
   154  * We have the two nodesets (number indicate "document position")
   155  * 
   156  * 1 3 7             <- source 1
   157  * 2 3 6 8 9         <- source 2
   158  * _ _ _ _ _ _ _ _   <- result
   159  * 
   160  * 
   161  * When merging these nodesets into the result, the nodes are transfered
   162  * in chunks to the end of the buffer so that each chunk does not contain
   163  * a node from the other nodeset, in document order.
   164  *
   165  * We select the last non-transfered node in the first nodeset and find
   166  * where in the other nodeset it would be inserted. In this case we would
   167  * take the 7 from the first nodeset and find the position between the
   168  * 6 and 8 in the second. We then take the nodes after the insert-position
   169  * and transfer them to the end of the resulting nodeset. Which in this case
   170  * means that we first transfered the 8 and 9 nodes, giving us the following:
   171  * 
   172  * 1 3 7             <- source 1
   173  * 2 3 6             <- source 2
   174  * _ _ _ _ _ _ 8 9   <- result
   175  *
   176  * The corresponding procedure is done for the second nodeset, that is
   177  * the insertion position of the 6 in the first nodeset is found, which
   178  * is between the 3 and the 7. The 7 is memmoved (as it stays within
   179  * the same nodeset) to the result buffer.
   180  *
   181  * As the result buffer is filled from the end, it is safe to share the
   182  * buffer between this nodeset and the result.
   183  * 
   184  * This is repeated until both of the nodesets are empty.
   185  *
   186  * If we find a duplicate node when searching for where insertposition we
   187  * check for sequences of duplicate nodes, which can be optimized.
   188  *
   189  */
   190 nsresult txNodeSet::add(const txNodeSet& aNodes, transferOp aTransfer,
   191                         destroyOp aDestroy)
   192 {
   193     NS_ASSERTION(mDirection == kForward,
   194                  "only append(aNode) is supported on reversed nodesets");
   196     if (aNodes.isEmpty()) {
   197         return NS_OK;
   198     }
   200     if (!ensureGrowSize(aNodes.size())) {
   201         return NS_ERROR_OUT_OF_MEMORY;
   202     }
   204     // This is probably a rather common case, so lets try to shortcut.
   205     if (mStart == mEnd ||
   206         txXPathNodeUtils::comparePosition(mEnd[-1], *aNodes.mStart) < 0) {
   207         aTransfer(mEnd, aNodes.mStart, aNodes.mEnd);
   208         mEnd += aNodes.size();
   210         return NS_OK;
   211     }
   213     // Last element in this nodeset
   214     txXPathNode* thisPos = mEnd;
   216     // Last element of the other nodeset
   217     txXPathNode* otherPos = aNodes.mEnd;
   219     // Pointer to the insertion point in this nodeset
   220     txXPathNode* insertPos = mEndBuffer;
   222     bool dupe;
   223     txXPathNode* pos;
   224     int32_t count;
   225     while (thisPos > mStart || otherPos > aNodes.mStart) {
   226         // Find where the last remaining node of this nodeset would
   227         // be inserted in the other nodeset.
   228         if (thisPos > mStart) {
   229             pos = findPosition(thisPos[-1], aNodes.mStart, otherPos, dupe);
   231             if (dupe) {
   232                 const txXPathNode *deletePos = thisPos;
   233                 --thisPos; // this is already added
   234                 // check dupe sequence
   235                 while (thisPos > mStart && pos > aNodes.mStart &&
   236                        thisPos[-1] == pos[-1]) {
   237                     --thisPos;
   238                     --pos;
   239                 }
   241                 if (aDestroy) {
   242                     aDestroy(thisPos, deletePos);
   243                 }
   244             }
   245         }
   246         else {
   247             pos = aNodes.mStart;
   248         }
   250         // Transfer the otherNodes after the insertion point to the result
   251         count = otherPos - pos;
   252         if (count > 0) {
   253             insertPos -= count;
   254             aTransfer(insertPos, pos, otherPos);
   255             otherPos -= count;
   256         }
   258         // Find where the last remaining node of the otherNodeset would
   259         // be inserted in this nodeset.
   260         if (otherPos > aNodes.mStart) {
   261             pos = findPosition(otherPos[-1], mStart, thisPos, dupe);
   263             if (dupe) {
   264                 const txXPathNode *deletePos = otherPos;
   265                 --otherPos; // this is already added
   266                 // check dupe sequence
   267                 while (otherPos > aNodes.mStart && pos > mStart &&
   268                        otherPos[-1] == pos[-1]) {
   269                     --otherPos;
   270                     --pos;
   271                 }
   273                 if (aDestroy) {
   274                     aDestroy(otherPos, deletePos);
   275                 }
   276             }
   277         }
   278         else {
   279             pos = mStart;
   280         }
   282         // Move the nodes from this nodeset after the insertion point
   283         // to the result
   284         count = thisPos - pos;
   285         if (count > 0) {
   286             insertPos -= count;
   287             LOG_CHUNK_MOVE(pos, insertPos, count);
   288             memmove(insertPos, pos, count * sizeof(txXPathNode));
   289             thisPos -= count;
   290         }
   291     }
   292     mStart = insertPos;
   293     mEnd = mEndBuffer;
   295     return NS_OK;
   296 }
   298 /**
   299  * Append API
   300  * These functions should be used with care.
   301  * They are intended to be used when the caller assures that the resulting
   302  * nodeset remains in document order.
   303  * Abuse will break document order, and cause errors in the result.
   304  * These functions are significantly faster than the add API, as no
   305  * order info operations will be performed.
   306  */
   308 nsresult
   309 txNodeSet::append(const txXPathNode& aNode)
   310 {
   311     if (!ensureGrowSize(1)) {
   312         return NS_ERROR_OUT_OF_MEMORY;
   313     }
   315     if (mDirection == kForward) {
   316         new(mEnd) txXPathNode(aNode);
   317         ++mEnd;
   319         return NS_OK;
   320     }
   322     new(--mStart) txXPathNode(aNode);
   324     return NS_OK;
   325 }
   327 nsresult
   328 txNodeSet::append(const txNodeSet& aNodes)
   329 {
   330     NS_ASSERTION(mDirection == kForward,
   331                  "only append(aNode) is supported on reversed nodesets");
   333     if (aNodes.isEmpty()) {
   334         return NS_OK;
   335     }
   337     int32_t appended = aNodes.size();
   338     if (!ensureGrowSize(appended)) {
   339         return NS_ERROR_OUT_OF_MEMORY;
   340     }
   342     copyElements(mEnd, aNodes.mStart, aNodes.mEnd);
   343     mEnd += appended;
   345     return NS_OK;
   346 }
   348 nsresult
   349 txNodeSet::mark(int32_t aIndex)
   350 {
   351     NS_ASSERTION(aIndex >= 0 && mStart && mEnd - mStart > aIndex,
   352                  "index out of bounds");
   353     if (!mMarks) {
   354         int32_t length = size();
   355         mMarks = new bool[length];
   356         NS_ENSURE_TRUE(mMarks, NS_ERROR_OUT_OF_MEMORY);
   357         memset(mMarks, 0, length * sizeof(bool));
   358     }
   359     if (mDirection == kForward) {
   360         mMarks[aIndex] = true;
   361     }
   362     else {
   363         mMarks[size() - aIndex - 1] = true;
   364     }
   366     return NS_OK;
   367 }
   369 nsresult
   370 txNodeSet::sweep()
   371 {
   372     if (!mMarks) {
   373         // sweep everything
   374         clear();
   375     }
   377     int32_t chunk, pos = 0;
   378     int32_t length = size();
   379     txXPathNode* insertion = mStartBuffer;
   381     while (pos < length) {
   382         while (pos < length && !mMarks[pos]) {
   383             // delete unmarked
   384             mStart[pos].~txXPathNode();
   385             ++pos;
   386         }
   387         // find chunk to move
   388         chunk = 0;
   389         while (pos < length && mMarks[pos]) {
   390             ++pos;
   391             ++chunk;
   392         }
   393         // move chunk
   394         if (chunk > 0) {
   395             LOG_CHUNK_MOVE(mStart + pos - chunk, insertion, chunk);
   396             memmove(insertion, mStart + pos - chunk,
   397                     chunk * sizeof(txXPathNode));
   398             insertion += chunk;
   399         }
   400     }
   401     mStart = mStartBuffer;
   402     mEnd = insertion;
   403     delete [] mMarks;
   404     mMarks = nullptr;
   406     return NS_OK;
   407 }
   409 void
   410 txNodeSet::clear()
   411 {
   412     destroyElements(mStart, mEnd);
   413 #ifdef TX_DONT_RECYCLE_BUFFER
   414     if (mStartBuffer) {
   415         nsMemory::Free(mStartBuffer);
   416         mStartBuffer = mEndBuffer = nullptr;
   417     }
   418 #endif
   419     mStart = mEnd = mStartBuffer;
   420     delete [] mMarks;
   421     mMarks = nullptr;
   422     mDirection = kForward;
   423 }
   425 int32_t
   426 txNodeSet::indexOf(const txXPathNode& aNode, uint32_t aStart) const
   427 {
   428     NS_ASSERTION(mDirection == kForward,
   429                  "only append(aNode) is supported on reversed nodesets");
   431     if (!mStart || mStart == mEnd) {
   432         return -1;
   433     }
   435     txXPathNode* pos = mStart + aStart;
   436     for (; pos < mEnd; ++pos) {
   437         if (*pos == aNode) {
   438             return pos - mStart;
   439         }
   440     }
   442     return -1;
   443 }
   445 const txXPathNode&
   446 txNodeSet::get(int32_t aIndex) const
   447 {
   448     if (mDirection == kForward) {
   449         return mStart[aIndex];
   450     }
   452     return mEnd[-aIndex - 1];
   453 }
   455 short
   456 txNodeSet::getResultType()
   457 {
   458     return txAExprResult::NODESET;
   459 }
   461 bool
   462 txNodeSet::booleanValue()
   463 {
   464     return !isEmpty();
   465 }
   466 double
   467 txNodeSet::numberValue()
   468 {
   469     nsAutoString str;
   470     stringValue(str);
   472     return txDouble::toDouble(str);
   473 }
   475 void
   476 txNodeSet::stringValue(nsString& aStr)
   477 {
   478     NS_ASSERTION(mDirection == kForward,
   479                  "only append(aNode) is supported on reversed nodesets");
   480     if (isEmpty()) {
   481         return;
   482     }
   483     txXPathNodeUtils::appendNodeValue(get(0), aStr);
   484 }
   486 const nsString*
   487 txNodeSet::stringValuePointer()
   488 {
   489     return nullptr;
   490 }
   492 bool txNodeSet::ensureGrowSize(int32_t aSize)
   493 {
   494     // check if there is enough place in the buffer as is
   495     if (mDirection == kForward && aSize <= mEndBuffer - mEnd) {
   496         return true;
   497     }
   499     if (mDirection == kReversed && aSize <= mStart - mStartBuffer) {
   500         return true;
   501     }
   503     // check if we just have to align mStart to have enough space
   504     int32_t oldSize = mEnd - mStart;
   505     int32_t oldLength = mEndBuffer - mStartBuffer;
   506     int32_t ensureSize = oldSize + aSize;
   507     if (ensureSize <= oldLength) {
   508         // just move the buffer
   509         txXPathNode* dest = mStartBuffer;
   510         if (mDirection == kReversed) {
   511             dest = mEndBuffer - oldSize;
   512         }
   513         LOG_CHUNK_MOVE(mStart, dest, oldSize);
   514         memmove(dest, mStart, oldSize * sizeof(txXPathNode));
   515         mStart = dest;
   516         mEnd = dest + oldSize;
   518         return true;
   519     }
   521     // This isn't 100% safe. But until someone manages to make a 1gig nodeset
   522     // it should be ok.
   523     int32_t newLength = std::max(oldLength, kTxNodeSetMinSize);
   525     while (newLength < ensureSize) {
   526         newLength *= kTxNodeSetGrowFactor;
   527     }
   529     txXPathNode* newArr = static_cast<txXPathNode*>
   530                                      (nsMemory::Alloc(newLength *
   531                                                          sizeof(txXPathNode)));
   532     if (!newArr) {
   533         return false;
   534     }
   536     txXPathNode* dest = newArr;
   537     if (mDirection == kReversed) {
   538         dest += newLength - oldSize;
   539     }
   541     if (oldSize > 0) {
   542         LOG_CHUNK_MOVE(mStart, dest, oldSize);
   543         memcpy(dest, mStart, oldSize * sizeof(txXPathNode));
   544     }
   546     if (mStartBuffer) {
   547 #ifdef DEBUG
   548         memset(mStartBuffer, 0,
   549                (mEndBuffer - mStartBuffer) * sizeof(txXPathNode));
   550 #endif
   551         nsMemory::Free(mStartBuffer);
   552     }
   554     mStartBuffer = newArr;
   555     mEndBuffer = mStartBuffer + newLength;
   556     mStart = dest;
   557     mEnd = dest + oldSize;
   559     return true;
   560 }
   562 txXPathNode*
   563 txNodeSet::findPosition(const txXPathNode& aNode, txXPathNode* aFirst,
   564                         txXPathNode* aLast, bool& aDupe) const
   565 {
   566     aDupe = false;
   567     if (aLast - aFirst <= 2) {
   568         // If we search 2 nodes or less there is no point in further divides
   569         txXPathNode* pos = aFirst;
   570         for (; pos < aLast; ++pos) {
   571             int cmp = txXPathNodeUtils::comparePosition(aNode, *pos);
   572             if (cmp < 0) {
   573                 return pos;
   574             }
   576             if (cmp == 0) {
   577                 aDupe = true;
   579                 return pos;
   580             }
   581         }
   582         return pos;
   583     }
   585     // (cannot add two pointers)
   586     txXPathNode* midpos = aFirst + (aLast - aFirst) / 2;
   587     int cmp = txXPathNodeUtils::comparePosition(aNode, *midpos);
   588     if (cmp == 0) {
   589         aDupe = true;
   591         return midpos;
   592     }
   594     if (cmp > 0) {
   595         return findPosition(aNode, midpos + 1, aLast, aDupe);
   596     }
   598     // midpos excluded as end of range
   600     return findPosition(aNode, aFirst, midpos, aDupe);
   601 }
   603 /* static */
   604 void
   605 txNodeSet::copyElements(txXPathNode* aDest,
   606                         const txXPathNode* aStart, const txXPathNode* aEnd)
   607 {
   608     const txXPathNode* pos = aStart;
   609     while (pos < aEnd) {
   610         new(aDest) txXPathNode(*pos);
   611         ++aDest;
   612         ++pos;
   613     }
   614 }
   616 /* static */
   617 void
   618 txNodeSet::transferElements(txXPathNode* aDest,
   619                             const txXPathNode* aStart, const txXPathNode* aEnd)
   620 {
   621     LOG_CHUNK_MOVE(aStart, aDest, (aEnd - aStart));
   622     memcpy(aDest, aStart, (aEnd - aStart) * sizeof(txXPathNode));
   623 }

mercurial