Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/xslt/xpath/txNodeSet.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
5
6
#include "txNodeSet.h"
7
#include "txLog.h"
8
#include "nsMemory.h"
9
#include "txXPathTreeWalker.h"
10
#include <algorithm>
11
12
/**
13
 * Implementation of an XPath nodeset
14
 */
15
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
33
34
static const int32_t kTxNodeSetMinSize = 4;
35
static const int32_t kTxNodeSetGrowFactor = 2;
36
37
0
#define kForward   1
38
0
#define kReversed -1
39
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
0
{
49
0
}
50
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
0
{
60
0
    if (!ensureGrowSize(1)) {
61
0
        return;
62
0
    }
63
0
64
0
    new(mStart) txXPathNode(aNode);
65
0
    ++mEnd;
66
0
}
67
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
0
{
77
0
    append(aSource);
78
0
}
79
80
txNodeSet::~txNodeSet()
81
0
{
82
0
    delete [] mMarks;
83
0
84
0
    if (mStartBuffer) {
85
0
        destroyElements(mStart, mEnd);
86
0
87
0
        free(mStartBuffer);
88
0
    }
89
0
}
90
91
nsresult txNodeSet::add(const txXPathNode& aNode)
92
0
{
93
0
    NS_ASSERTION(mDirection == kForward,
94
0
                 "only append(aNode) is supported on reversed nodesets");
95
0
96
0
    if (isEmpty()) {
97
0
        return append(aNode);
98
0
    }
99
0
100
0
    bool dupe;
101
0
    txXPathNode* pos = findPosition(aNode, mStart, mEnd, dupe);
102
0
103
0
    if (dupe) {
104
0
        return NS_OK;
105
0
    }
106
0
107
0
    // save pos, ensureGrowSize messes with the pointers
108
0
    int32_t moveSize = mEnd - pos;
109
0
    int32_t offset = pos - mStart;
110
0
    if (!ensureGrowSize(1)) {
111
0
        return NS_ERROR_OUT_OF_MEMORY;
112
0
    }
113
0
    // set pos to where it was
114
0
    pos = mStart + offset;
115
0
116
0
    if (moveSize > 0) {
117
0
        LOG_CHUNK_MOVE(pos, pos + 1, moveSize);
118
0
        memmove(pos + 1, pos, moveSize * sizeof(txXPathNode));
119
0
    }
120
0
121
0
    new(pos) txXPathNode(aNode);
122
0
    ++mEnd;
123
0
124
0
    return NS_OK;
125
0
}
126
127
nsresult txNodeSet::add(const txNodeSet& aNodes)
128
0
{
129
0
    return add(aNodes, copyElements, nullptr);
130
0
}
131
132
nsresult txNodeSet::addAndTransfer(txNodeSet* aNodes)
133
0
{
134
0
    // failure is out-of-memory, transfer didn't happen
135
0
    nsresult rv = add(*aNodes, transferElements, destroyElements);
136
0
    NS_ENSURE_SUCCESS(rv, rv);
137
0
138
#ifdef TX_DONT_RECYCLE_BUFFER
139
    if (aNodes->mStartBuffer) {
140
        free(aNodes->mStartBuffer);
141
        aNodes->mStartBuffer = aNodes->mEndBuffer = nullptr;
142
    }
143
#endif
144
0
    aNodes->mStart = aNodes->mEnd = aNodes->mStartBuffer;
145
0
146
0
    return NS_OK;
147
0
}
148
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
0
{
193
0
    NS_ASSERTION(mDirection == kForward,
194
0
                 "only append(aNode) is supported on reversed nodesets");
195
0
196
0
    if (aNodes.isEmpty()) {
197
0
        return NS_OK;
198
0
    }
199
0
200
0
    if (!ensureGrowSize(aNodes.size())) {
201
0
        return NS_ERROR_OUT_OF_MEMORY;
202
0
    }
203
0
204
0
    // This is probably a rather common case, so lets try to shortcut.
205
0
    if (mStart == mEnd ||
206
0
        txXPathNodeUtils::comparePosition(mEnd[-1], *aNodes.mStart) < 0) {
207
0
        aTransfer(mEnd, aNodes.mStart, aNodes.mEnd);
208
0
        mEnd += aNodes.size();
209
0
210
0
        return NS_OK;
211
0
    }
212
0
213
0
    // Last element in this nodeset
214
0
    txXPathNode* thisPos = mEnd;
215
0
216
0
    // Last element of the other nodeset
217
0
    txXPathNode* otherPos = aNodes.mEnd;
218
0
219
0
    // Pointer to the insertion point in this nodeset
220
0
    txXPathNode* insertPos = mEndBuffer;
221
0
222
0
    bool dupe;
223
0
    txXPathNode* pos;
224
0
    int32_t count;
225
0
    while (thisPos > mStart || otherPos > aNodes.mStart) {
226
0
        // Find where the last remaining node of this nodeset would
227
0
        // be inserted in the other nodeset.
228
0
        if (thisPos > mStart) {
229
0
            pos = findPosition(thisPos[-1], aNodes.mStart, otherPos, dupe);
230
0
231
0
            if (dupe) {
232
0
                const txXPathNode *deletePos = thisPos;
233
0
                --thisPos; // this is already added
234
0
                // check dupe sequence
235
0
                while (thisPos > mStart && pos > aNodes.mStart &&
236
0
                       thisPos[-1] == pos[-1]) {
237
0
                    --thisPos;
238
0
                    --pos;
239
0
                }
240
0
241
0
                if (aDestroy) {
242
0
                    aDestroy(thisPos, deletePos);
243
0
                }
244
0
            }
245
0
        }
246
0
        else {
247
0
            pos = aNodes.mStart;
248
0
        }
249
0
250
0
        // Transfer the otherNodes after the insertion point to the result
251
0
        count = otherPos - pos;
252
0
        if (count > 0) {
253
0
            insertPos -= count;
254
0
            aTransfer(insertPos, pos, otherPos);
255
0
            otherPos -= count;
256
0
        }
257
0
258
0
        // Find where the last remaining node of the otherNodeset would
259
0
        // be inserted in this nodeset.
260
0
        if (otherPos > aNodes.mStart) {
261
0
            pos = findPosition(otherPos[-1], mStart, thisPos, dupe);
262
0
263
0
            if (dupe) {
264
0
                const txXPathNode *deletePos = otherPos;
265
0
                --otherPos; // this is already added
266
0
                // check dupe sequence
267
0
                while (otherPos > aNodes.mStart && pos > mStart &&
268
0
                       otherPos[-1] == pos[-1]) {
269
0
                    --otherPos;
270
0
                    --pos;
271
0
                }
272
0
273
0
                if (aDestroy) {
274
0
                    aDestroy(otherPos, deletePos);
275
0
                }
276
0
            }
277
0
        }
278
0
        else {
279
0
            pos = mStart;
280
0
        }
281
0
282
0
        // Move the nodes from this nodeset after the insertion point
283
0
        // to the result
284
0
        count = thisPos - pos;
285
0
        if (count > 0) {
286
0
            insertPos -= count;
287
0
            LOG_CHUNK_MOVE(pos, insertPos, count);
288
0
            memmove(insertPos, pos, count * sizeof(txXPathNode));
289
0
            thisPos -= count;
290
0
        }
291
0
    }
292
0
    mStart = insertPos;
293
0
    mEnd = mEndBuffer;
294
0
295
0
    return NS_OK;
296
0
}
297
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
 */
307
308
nsresult
309
txNodeSet::append(const txXPathNode& aNode)
310
0
{
311
0
    if (!ensureGrowSize(1)) {
312
0
        return NS_ERROR_OUT_OF_MEMORY;
313
0
    }
314
0
315
0
    if (mDirection == kForward) {
316
0
        new(mEnd) txXPathNode(aNode);
317
0
        ++mEnd;
318
0
319
0
        return NS_OK;
320
0
    }
321
0
322
0
    new(--mStart) txXPathNode(aNode);
323
0
324
0
    return NS_OK;
325
0
}
326
327
nsresult
328
txNodeSet::append(const txNodeSet& aNodes)
329
0
{
330
0
    NS_ASSERTION(mDirection == kForward,
331
0
                 "only append(aNode) is supported on reversed nodesets");
332
0
333
0
    if (aNodes.isEmpty()) {
334
0
        return NS_OK;
335
0
    }
336
0
337
0
    int32_t appended = aNodes.size();
338
0
    if (!ensureGrowSize(appended)) {
339
0
        return NS_ERROR_OUT_OF_MEMORY;
340
0
    }
341
0
342
0
    copyElements(mEnd, aNodes.mStart, aNodes.mEnd);
343
0
    mEnd += appended;
344
0
345
0
    return NS_OK;
346
0
}
347
348
nsresult
349
txNodeSet::mark(int32_t aIndex)
350
0
{
351
0
    NS_ASSERTION(aIndex >= 0 && mStart && mEnd - mStart > aIndex,
352
0
                 "index out of bounds");
353
0
    if (!mMarks) {
354
0
        int32_t length = size();
355
0
        mMarks = new bool[length];
356
0
        memset(mMarks, 0, length * sizeof(bool));
357
0
    }
358
0
    if (mDirection == kForward) {
359
0
        mMarks[aIndex] = true;
360
0
    }
361
0
    else {
362
0
        mMarks[size() - aIndex - 1] = true;
363
0
    }
364
0
365
0
    return NS_OK;
366
0
}
367
368
nsresult
369
txNodeSet::sweep()
370
0
{
371
0
    if (!mMarks) {
372
0
        // sweep everything
373
0
        clear();
374
0
    }
375
0
376
0
    int32_t chunk, pos = 0;
377
0
    int32_t length = size();
378
0
    txXPathNode* insertion = mStartBuffer;
379
0
380
0
    while (pos < length) {
381
0
        while (pos < length && !mMarks[pos]) {
382
0
            // delete unmarked
383
0
            mStart[pos].~txXPathNode();
384
0
            ++pos;
385
0
        }
386
0
        // find chunk to move
387
0
        chunk = 0;
388
0
        while (pos < length && mMarks[pos]) {
389
0
            ++pos;
390
0
            ++chunk;
391
0
        }
392
0
        // move chunk
393
0
        if (chunk > 0) {
394
0
            LOG_CHUNK_MOVE(mStart + pos - chunk, insertion, chunk);
395
0
            memmove(insertion, mStart + pos - chunk,
396
0
                    chunk * sizeof(txXPathNode));
397
0
            insertion += chunk;
398
0
        }
399
0
    }
400
0
    mStart = mStartBuffer;
401
0
    mEnd = insertion;
402
0
    delete [] mMarks;
403
0
    mMarks = nullptr;
404
0
405
0
    return NS_OK;
406
0
}
407
408
void
409
txNodeSet::clear()
410
0
{
411
0
    destroyElements(mStart, mEnd);
412
#ifdef TX_DONT_RECYCLE_BUFFER
413
    if (mStartBuffer) {
414
        free(mStartBuffer);
415
        mStartBuffer = mEndBuffer = nullptr;
416
    }
417
#endif
418
    mStart = mEnd = mStartBuffer;
419
0
    delete [] mMarks;
420
0
    mMarks = nullptr;
421
0
    mDirection = kForward;
422
0
}
423
424
int32_t
425
txNodeSet::indexOf(const txXPathNode& aNode, uint32_t aStart) const
426
0
{
427
0
    NS_ASSERTION(mDirection == kForward,
428
0
                 "only append(aNode) is supported on reversed nodesets");
429
0
430
0
    if (!mStart || mStart == mEnd) {
431
0
        return -1;
432
0
    }
433
0
434
0
    txXPathNode* pos = mStart + aStart;
435
0
    for (; pos < mEnd; ++pos) {
436
0
        if (*pos == aNode) {
437
0
            return pos - mStart;
438
0
        }
439
0
    }
440
0
441
0
    return -1;
442
0
}
443
444
const txXPathNode&
445
txNodeSet::get(int32_t aIndex) const
446
0
{
447
0
    if (mDirection == kForward) {
448
0
        return mStart[aIndex];
449
0
    }
450
0
451
0
    return mEnd[-aIndex - 1];
452
0
}
453
454
short
455
txNodeSet::getResultType()
456
0
{
457
0
    return txAExprResult::NODESET;
458
0
}
459
460
bool
461
txNodeSet::booleanValue()
462
0
{
463
0
    return !isEmpty();
464
0
}
465
double
466
txNodeSet::numberValue()
467
0
{
468
0
    nsAutoString str;
469
0
    stringValue(str);
470
0
471
0
    return txDouble::toDouble(str);
472
0
}
473
474
void
475
txNodeSet::stringValue(nsString& aStr)
476
0
{
477
0
    NS_ASSERTION(mDirection == kForward,
478
0
                 "only append(aNode) is supported on reversed nodesets");
479
0
    if (isEmpty()) {
480
0
        return;
481
0
    }
482
0
    txXPathNodeUtils::appendNodeValue(get(0), aStr);
483
0
}
484
485
const nsString*
486
txNodeSet::stringValuePointer()
487
0
{
488
0
    return nullptr;
489
0
}
490
491
bool txNodeSet::ensureGrowSize(int32_t aSize)
492
0
{
493
0
    // check if there is enough place in the buffer as is
494
0
    if (mDirection == kForward && aSize <= mEndBuffer - mEnd) {
495
0
        return true;
496
0
    }
497
0
498
0
    if (mDirection == kReversed && aSize <= mStart - mStartBuffer) {
499
0
        return true;
500
0
    }
501
0
502
0
    // check if we just have to align mStart to have enough space
503
0
    int32_t oldSize = mEnd - mStart;
504
0
    int32_t oldLength = mEndBuffer - mStartBuffer;
505
0
    int32_t ensureSize = oldSize + aSize;
506
0
    if (ensureSize <= oldLength) {
507
0
        // just move the buffer
508
0
        txXPathNode* dest = mStartBuffer;
509
0
        if (mDirection == kReversed) {
510
0
            dest = mEndBuffer - oldSize;
511
0
        }
512
0
        LOG_CHUNK_MOVE(mStart, dest, oldSize);
513
0
        memmove(dest, mStart, oldSize * sizeof(txXPathNode));
514
0
        mStart = dest;
515
0
        mEnd = dest + oldSize;
516
0
517
0
        return true;
518
0
    }
519
0
520
0
    // This isn't 100% safe. But until someone manages to make a 1gig nodeset
521
0
    // it should be ok.
522
0
    int32_t newLength = std::max(oldLength, kTxNodeSetMinSize);
523
0
524
0
    while (newLength < ensureSize) {
525
0
        newLength *= kTxNodeSetGrowFactor;
526
0
    }
527
0
528
0
    txXPathNode* newArr =
529
0
      static_cast<txXPathNode*>(moz_xmalloc(newLength * sizeof(txXPathNode)));
530
0
531
0
    txXPathNode* dest = newArr;
532
0
    if (mDirection == kReversed) {
533
0
        dest += newLength - oldSize;
534
0
    }
535
0
536
0
    if (oldSize > 0) {
537
0
        LOG_CHUNK_MOVE(mStart, dest, oldSize);
538
0
        memcpy(dest, mStart, oldSize * sizeof(txXPathNode));
539
0
    }
540
0
541
0
    if (mStartBuffer) {
542
#ifdef DEBUG
543
        memset(mStartBuffer, 0,
544
               (mEndBuffer - mStartBuffer) * sizeof(txXPathNode));
545
#endif
546
        free(mStartBuffer);
547
0
    }
548
0
549
0
    mStartBuffer = newArr;
550
0
    mEndBuffer = mStartBuffer + newLength;
551
0
    mStart = dest;
552
0
    mEnd = dest + oldSize;
553
0
554
0
    return true;
555
0
}
556
557
txXPathNode*
558
txNodeSet::findPosition(const txXPathNode& aNode, txXPathNode* aFirst,
559
                        txXPathNode* aLast, bool& aDupe) const
560
0
{
561
0
    aDupe = false;
562
0
    if (aLast - aFirst <= 2) {
563
0
        // If we search 2 nodes or less there is no point in further divides
564
0
        txXPathNode* pos = aFirst;
565
0
        for (; pos < aLast; ++pos) {
566
0
            int cmp = txXPathNodeUtils::comparePosition(aNode, *pos);
567
0
            if (cmp < 0) {
568
0
                return pos;
569
0
            }
570
0
571
0
            if (cmp == 0) {
572
0
                aDupe = true;
573
0
574
0
                return pos;
575
0
            }
576
0
        }
577
0
        return pos;
578
0
    }
579
0
580
0
    // (cannot add two pointers)
581
0
    txXPathNode* midpos = aFirst + (aLast - aFirst) / 2;
582
0
    int cmp = txXPathNodeUtils::comparePosition(aNode, *midpos);
583
0
    if (cmp == 0) {
584
0
        aDupe = true;
585
0
586
0
        return midpos;
587
0
    }
588
0
589
0
    if (cmp > 0) {
590
0
        return findPosition(aNode, midpos + 1, aLast, aDupe);
591
0
    }
592
0
593
0
    // midpos excluded as end of range
594
0
595
0
    return findPosition(aNode, aFirst, midpos, aDupe);
596
0
}
597
598
/* static */
599
void
600
txNodeSet::copyElements(txXPathNode* aDest,
601
                        const txXPathNode* aStart, const txXPathNode* aEnd)
602
0
{
603
0
    const txXPathNode* pos = aStart;
604
0
    while (pos < aEnd) {
605
0
        new(aDest) txXPathNode(*pos);
606
0
        ++aDest;
607
0
        ++pos;
608
0
    }
609
0
}
610
611
/* static */
612
void
613
txNodeSet::transferElements(txXPathNode* aDest,
614
                            const txXPathNode* aStart, const txXPathNode* aEnd)
615
0
{
616
0
    LOG_CHUNK_MOVE(aStart, aDest, (aEnd - aStart));
617
0
    memcpy(aDest, aStart, (aEnd - aStart) * sizeof(txXPathNode));
618
0
}