Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/parser/htmlparser/nsScannerString.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim:set ts=2 sw=2 sts=2 et cindent: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include <stdlib.h>
8
#include "nsScannerString.h"
9
#include "mozilla/CheckedInt.h"
10
11
12
  /**
13
   * nsScannerBufferList
14
   */
15
16
0
#define MAX_CAPACITY ((UINT32_MAX / sizeof(char16_t)) - \
17
0
                      (sizeof(Buffer) + sizeof(char16_t)))
18
19
nsScannerBufferList::Buffer*
20
nsScannerBufferList::AllocBufferFromString( const nsAString& aString )
21
0
  {
22
0
    uint32_t len = aString.Length();
23
0
    Buffer* buf = AllocBuffer(len);
24
0
25
0
    if (buf)
26
0
      {
27
0
        nsAString::const_iterator source;
28
0
        aString.BeginReading(source);
29
0
        nsCharTraits<char16_t>::copy(buf->DataStart(), source.get(), len);
30
0
      }
31
0
    return buf;
32
0
  }
33
34
nsScannerBufferList::Buffer*
35
nsScannerBufferList::AllocBuffer( uint32_t capacity )
36
0
  {
37
0
    if (capacity > MAX_CAPACITY)
38
0
      return nullptr;
39
0
40
0
    void* ptr = malloc(sizeof(Buffer) + (capacity + 1) * sizeof(char16_t));
41
0
    if (!ptr)
42
0
      return nullptr;
43
0
44
0
    Buffer* buf = new (ptr) Buffer();
45
0
46
0
    buf->mUsageCount = 0;
47
0
    buf->mDataEnd = buf->DataStart() + capacity;
48
0
49
0
    // XXX null terminate.  this shouldn't be required, but we do it because
50
0
    // nsScanner erroneously thinks it can dereference DataEnd :-(
51
0
    *buf->mDataEnd = char16_t(0);
52
0
    return buf;
53
0
  }
54
55
void
56
nsScannerBufferList::ReleaseAll()
57
0
  {
58
0
    while (!mBuffers.isEmpty())
59
0
      {
60
0
        Buffer* node = mBuffers.popFirst();
61
0
        //printf(">>> freeing buffer @%p\n", node);
62
0
        free(node);
63
0
      }
64
0
  }
65
66
void
67
nsScannerBufferList::SplitBuffer( const Position& pos )
68
0
  {
69
0
    // splitting to the right keeps the work string and any extant token
70
0
    // pointing to and holding a reference count on the same buffer.
71
0
72
0
    Buffer* bufferToSplit = pos.mBuffer;
73
0
    NS_ASSERTION(bufferToSplit, "null pointer");
74
0
75
0
    uint32_t splitOffset = pos.mPosition - bufferToSplit->DataStart();
76
0
    NS_ASSERTION(pos.mPosition >= bufferToSplit->DataStart() &&
77
0
                 splitOffset <= bufferToSplit->DataLength(),
78
0
                 "split offset is outside buffer");
79
0
    
80
0
    uint32_t len = bufferToSplit->DataLength() - splitOffset;
81
0
    Buffer* new_buffer = AllocBuffer(len);
82
0
    if (new_buffer)
83
0
      {
84
0
        nsCharTraits<char16_t>::copy(new_buffer->DataStart(),
85
0
                                      bufferToSplit->DataStart() + splitOffset,
86
0
                                      len);
87
0
        InsertAfter(new_buffer, bufferToSplit);
88
0
        bufferToSplit->SetDataLength(splitOffset);
89
0
      }
90
0
  }
91
92
void
93
nsScannerBufferList::DiscardUnreferencedPrefix( Buffer* aBuf )
94
0
  {
95
0
    if (aBuf == Head())
96
0
      {
97
0
        while (!mBuffers.isEmpty() && !Head()->IsInUse())
98
0
          {
99
0
            Buffer* buffer = Head();
100
0
            buffer->remove();
101
0
            free(buffer);
102
0
          }
103
0
      }
104
0
  }
105
106
size_t
107
nsScannerBufferList::Position::Distance( const Position& aStart, const Position& aEnd )
108
0
  {
109
0
    size_t result = 0;
110
0
    if (aStart.mBuffer == aEnd.mBuffer)
111
0
      {
112
0
        result = aEnd.mPosition - aStart.mPosition;
113
0
      }
114
0
    else
115
0
      {
116
0
        result = aStart.mBuffer->DataEnd() - aStart.mPosition;
117
0
        for (Buffer* b = aStart.mBuffer->Next(); b != aEnd.mBuffer; b = b->Next())
118
0
          result += b->DataLength();
119
0
        result += aEnd.mPosition - aEnd.mBuffer->DataStart();
120
0
      }
121
0
    return result;
122
0
  }
123
124
125
/**
126
 * nsScannerSubstring
127
 */
128
129
nsScannerSubstring::nsScannerSubstring()
130
  : mStart(nullptr, nullptr)
131
  , mEnd(nullptr, nullptr)
132
  , mBufferList(nullptr)
133
  , mLength(0)
134
  , mIsDirty(true)
135
0
  {
136
0
  }
137
138
nsScannerSubstring::nsScannerSubstring( const nsAString& s )
139
  : mBufferList(nullptr)
140
  , mIsDirty(true)
141
0
  {
142
0
    Rebind(s);
143
0
  }
144
145
nsScannerSubstring::~nsScannerSubstring()
146
0
  {
147
0
    release_ownership_of_buffer_list();
148
0
  }
149
150
int32_t
151
nsScannerSubstring::CountChar( char16_t c ) const
152
0
  {
153
0
      /*
154
0
        re-write this to use a counting sink
155
0
       */
156
0
157
0
    size_type result = 0;
158
0
    size_type lengthToExamine = Length();
159
0
160
0
    nsScannerIterator iter;
161
0
    for ( BeginReading(iter); ; )
162
0
      {
163
0
        int32_t lengthToExamineInThisFragment = iter.size_forward();
164
0
        const char16_t* fromBegin = iter.get();
165
0
        result += size_type(NS_COUNT(fromBegin, fromBegin+lengthToExamineInThisFragment, c));
166
0
        if ( !(lengthToExamine -= lengthToExamineInThisFragment) )
167
0
          return result;
168
0
        iter.advance(lengthToExamineInThisFragment);
169
0
      }
170
0
      // never reached; quiets warnings
171
0
    return 0;
172
0
  }
173
174
void
175
nsScannerSubstring::Rebind( const nsScannerSubstring& aString,
176
                            const nsScannerIterator& aStart, 
177
                            const nsScannerIterator& aEnd )
178
0
  {
179
0
    // allow for the case where &aString == this
180
0
181
0
    aString.acquire_ownership_of_buffer_list();
182
0
    release_ownership_of_buffer_list();
183
0
184
0
    mStart      = aStart;
185
0
    mEnd        = aEnd;
186
0
    mBufferList = aString.mBufferList;
187
0
    mLength     = Distance(aStart, aEnd);
188
0
    mIsDirty    = true;
189
0
  }
190
191
void
192
nsScannerSubstring::Rebind( const nsAString& aString )
193
0
  {
194
0
    release_ownership_of_buffer_list();
195
0
196
0
    mBufferList = new nsScannerBufferList(AllocBufferFromString(aString));
197
0
    mIsDirty    = true;
198
0
199
0
    init_range_from_buffer_list();
200
0
    acquire_ownership_of_buffer_list();
201
0
  }
202
203
const nsAString&
204
nsScannerSubstring::AsString() const
205
0
  {
206
0
    if (mIsDirty)
207
0
      {
208
0
        nsScannerSubstring* mutable_this = const_cast<nsScannerSubstring*>(this);
209
0
210
0
        if (mStart.mBuffer == mEnd.mBuffer) {
211
0
          // We only have a single fragment to deal with, so just return it
212
0
          // as a substring.
213
0
          mutable_this->mFlattenedRep.Rebind(mStart.mPosition, mEnd.mPosition);
214
0
        } else {
215
0
          // Otherwise, we need to copy the data into a flattened buffer.
216
0
          nsScannerIterator start, end;
217
0
          CopyUnicodeTo(BeginReading(start), EndReading(end), mutable_this->mFlattenedRep);
218
0
        }
219
0
220
0
        mutable_this->mIsDirty = false;
221
0
      }
222
0
223
0
    return mFlattenedRep;
224
0
  }
225
226
nsScannerIterator&
227
nsScannerSubstring::BeginReading( nsScannerIterator& iter ) const
228
0
  {
229
0
    iter.mOwner = this;
230
0
231
0
    iter.mFragment.mBuffer = mStart.mBuffer;
232
0
    iter.mFragment.mFragmentStart = mStart.mPosition;
233
0
    if (mStart.mBuffer == mEnd.mBuffer)
234
0
      iter.mFragment.mFragmentEnd = mEnd.mPosition;
235
0
    else
236
0
      iter.mFragment.mFragmentEnd = mStart.mBuffer->DataEnd();
237
0
238
0
    iter.mPosition = mStart.mPosition;
239
0
    iter.normalize_forward();
240
0
    return iter;
241
0
  }
242
243
nsScannerIterator&
244
nsScannerSubstring::EndReading( nsScannerIterator& iter ) const
245
0
  {
246
0
    iter.mOwner = this;
247
0
248
0
    iter.mFragment.mBuffer = mEnd.mBuffer;
249
0
    iter.mFragment.mFragmentEnd = mEnd.mPosition;
250
0
    if (mStart.mBuffer == mEnd.mBuffer)
251
0
      iter.mFragment.mFragmentStart = mStart.mPosition;
252
0
    else
253
0
      iter.mFragment.mFragmentStart = mEnd.mBuffer->DataStart();
254
0
255
0
    iter.mPosition = mEnd.mPosition;
256
0
    // must not |normalize_backward| as that would likely invalidate tests like |while ( first != last )|
257
0
    return iter;
258
0
  }
259
260
bool
261
nsScannerSubstring::GetNextFragment( nsScannerFragment& frag ) const
262
0
  {
263
0
    // check to see if we are at the end of the buffer list
264
0
    if (frag.mBuffer == mEnd.mBuffer)
265
0
      return false;
266
0
267
0
    frag.mBuffer = frag.mBuffer->getNext();
268
0
269
0
    if (frag.mBuffer == mStart.mBuffer)
270
0
      frag.mFragmentStart = mStart.mPosition;
271
0
    else
272
0
      frag.mFragmentStart = frag.mBuffer->DataStart();
273
0
274
0
    if (frag.mBuffer == mEnd.mBuffer)
275
0
      frag.mFragmentEnd = mEnd.mPosition;
276
0
    else
277
0
      frag.mFragmentEnd = frag.mBuffer->DataEnd();
278
0
279
0
    return true;
280
0
  }
281
282
bool
283
nsScannerSubstring::GetPrevFragment( nsScannerFragment& frag ) const
284
0
  {
285
0
    // check to see if we are at the beginning of the buffer list
286
0
    if (frag.mBuffer == mStart.mBuffer)
287
0
      return false;
288
0
289
0
    frag.mBuffer = frag.mBuffer->getPrevious();
290
0
291
0
    if (frag.mBuffer == mStart.mBuffer)
292
0
      frag.mFragmentStart = mStart.mPosition;
293
0
    else
294
0
      frag.mFragmentStart = frag.mBuffer->DataStart();
295
0
296
0
    if (frag.mBuffer == mEnd.mBuffer)
297
0
      frag.mFragmentEnd = mEnd.mPosition;
298
0
    else
299
0
      frag.mFragmentEnd = frag.mBuffer->DataEnd();
300
0
301
0
    return true;
302
0
  }
303
304
305
  /**
306
   * nsScannerString
307
   */
308
309
nsScannerString::nsScannerString( Buffer* aBuf )
310
0
  {
311
0
    mBufferList = new nsScannerBufferList(aBuf);
312
0
313
0
    init_range_from_buffer_list();
314
0
    acquire_ownership_of_buffer_list();
315
0
  }
316
317
void
318
nsScannerString::AppendBuffer( Buffer* aBuf )
319
0
  {
320
0
    mBufferList->Append(aBuf);
321
0
    mLength += aBuf->DataLength();
322
0
323
0
    mEnd.mBuffer = aBuf;
324
0
    mEnd.mPosition = aBuf->DataEnd();
325
0
326
0
    mIsDirty = true;
327
0
  }
328
329
void
330
nsScannerString::DiscardPrefix( const nsScannerIterator& aIter )
331
0
  {
332
0
    Position old_start(mStart);
333
0
    mStart = aIter;
334
0
    mLength -= Position::Distance(old_start, mStart);
335
0
    
336
0
    mStart.mBuffer->IncrementUsageCount();
337
0
    old_start.mBuffer->DecrementUsageCount();
338
0
339
0
    mBufferList->DiscardUnreferencedPrefix(old_start.mBuffer);
340
0
341
0
    mIsDirty = true;
342
0
  }
343
344
void
345
nsScannerString::UngetReadable( const nsAString& aReadable, const nsScannerIterator& aInsertPoint )
346
    /*
347
     * Warning: this routine manipulates the shared buffer list in an unexpected way.
348
     *  The original design did not really allow for insertions, but this call promises
349
     *  that if called for a point after the end of all extant token strings, that no token string
350
     *  or the work string will be invalidated.
351
     *
352
     *  This routine is protected because it is the responsibility of the derived class to keep those promises.
353
     */
354
0
  {
355
0
    Position insertPos(aInsertPoint);
356
0
357
0
    mBufferList->SplitBuffer(insertPos);
358
0
      // splitting to the right keeps the work string and any extant token pointing to and
359
0
      //  holding a reference count on the same buffer
360
0
361
0
    Buffer* new_buffer = AllocBufferFromString(aReadable);
362
0
      // make a new buffer with all the data to insert...
363
0
      //  BULLSHIT ALERT: we may have empty space to re-use in the split buffer, measure the cost
364
0
      //  of this and decide if we should do the work to fill it
365
0
366
0
    Buffer* buffer_to_split = insertPos.mBuffer;
367
0
    mBufferList->InsertAfter(new_buffer, buffer_to_split);
368
0
    mLength += aReadable.Length();
369
0
370
0
    mEnd.mBuffer = mBufferList->Tail();
371
0
    mEnd.mPosition = mEnd.mBuffer->DataEnd();
372
0
373
0
    mIsDirty = true;
374
0
  }
375
376
  /**
377
   * nsScannerSharedSubstring
378
   */
379
380
void
381
nsScannerSharedSubstring::Rebind(const nsScannerIterator &aStart,
382
                              const nsScannerIterator &aEnd)
383
0
{
384
0
  // If the start and end positions are inside the same buffer, we must
385
0
  // acquire ownership of the buffer.  If not, we can optimize by not holding
386
0
  // onto it.
387
0
388
0
  Buffer *buffer = const_cast<Buffer*>(aStart.buffer());
389
0
  bool sameBuffer = buffer == aEnd.buffer();
390
0
391
0
  nsScannerBufferList *bufferList;
392
0
393
0
  if (sameBuffer) {
394
0
    bufferList = aStart.mOwner->mBufferList;
395
0
    bufferList->AddRef();
396
0
    buffer->IncrementUsageCount();
397
0
  }
398
0
399
0
  if (mBufferList)
400
0
    ReleaseBuffer();
401
0
402
0
  if (sameBuffer) {
403
0
    mBuffer = buffer;
404
0
    mBufferList = bufferList;
405
0
    mString.Rebind(aStart.mPosition, aEnd.mPosition);
406
0
  } else {
407
0
    mBuffer = nullptr;
408
0
    mBufferList = nullptr;
409
0
    CopyUnicodeTo(aStart, aEnd, mString);
410
0
  }
411
0
}
412
413
void
414
nsScannerSharedSubstring::ReleaseBuffer()
415
0
{
416
0
  NS_ASSERTION(mBufferList, "Should only be called with non-null mBufferList");
417
0
  mBuffer->DecrementUsageCount();
418
0
  mBufferList->DiscardUnreferencedPrefix(mBuffer);
419
0
  mBufferList->Release();
420
0
}
421
422
void
423
nsScannerSharedSubstring::MakeMutable()
424
0
{
425
0
  nsString temp(mString); // this will force a copy of the data
426
0
  mString.Assign(temp);   // mString will now share the just-allocated buffer
427
0
428
0
  ReleaseBuffer();
429
0
430
0
  mBuffer = nullptr;
431
0
  mBufferList = nullptr;
432
0
}
433
434
  /**
435
   * utils -- based on code from nsReadableUtils.cpp
436
   */
437
438
// private helper function
439
static inline
440
nsAString::iterator&
441
copy_multifragment_string( nsScannerIterator& first, const nsScannerIterator& last, nsAString::iterator& result )
442
0
  {
443
0
    typedef nsCharSourceTraits<nsScannerIterator> source_traits;
444
0
    typedef nsCharSinkTraits<nsAString::iterator> sink_traits;
445
0
446
0
    while ( first != last )
447
0
      {
448
0
        uint32_t distance = source_traits::readable_distance(first, last);
449
0
        sink_traits::write(result, source_traits::read(first), distance);
450
0
        NS_ASSERTION(distance > 0, "|copy_multifragment_string| will never terminate");
451
0
        source_traits::advance(first, distance);
452
0
      }
453
0
454
0
    return result;
455
0
  }
456
457
bool
458
CopyUnicodeTo( const nsScannerIterator& aSrcStart,
459
               const nsScannerIterator& aSrcEnd,
460
               nsAString& aDest )
461
0
  {
462
0
    mozilla::CheckedInt<nsAString::size_type> distance(Distance(aSrcStart, aSrcEnd));
463
0
    if (!distance.isValid()) {
464
0
      return false; // overflow detected
465
0
    }
466
0
467
0
    if (!aDest.SetLength(distance.value(), mozilla::fallible)) {
468
0
      aDest.Truncate();
469
0
      return false; // out of memory
470
0
    }
471
0
    auto writer = aDest.BeginWriting();
472
0
    nsScannerIterator fromBegin(aSrcStart);
473
0
474
0
    copy_multifragment_string(fromBegin, aSrcEnd, writer);
475
0
    return true;
476
0
  }
477
478
bool
479
AppendUnicodeTo( const nsScannerIterator& aSrcStart,
480
                 const nsScannerIterator& aSrcEnd,
481
                 nsScannerSharedSubstring& aDest )
482
0
  {
483
0
    // Check whether we can just create a dependent string.
484
0
    if (aDest.str().IsEmpty()) {
485
0
      // We can just make |aDest| point to the buffer.
486
0
      // This will take care of copying if the buffer spans fragments.
487
0
      aDest.Rebind(aSrcStart, aSrcEnd);
488
0
      return true;
489
0
    }
490
0
    // The dest string is not empty, so it can't be a dependent substring.
491
0
    return AppendUnicodeTo(aSrcStart, aSrcEnd, aDest.writable());
492
0
  }
493
494
bool
495
AppendUnicodeTo( const nsScannerIterator& aSrcStart,
496
                 const nsScannerIterator& aSrcEnd,
497
                 nsAString& aDest )
498
0
  {
499
0
    const nsAString::size_type oldLength = aDest.Length();
500
0
    CheckedInt<nsAString::size_type> newLen(Distance(aSrcStart, aSrcEnd));
501
0
    newLen += oldLength;
502
0
    if (!newLen.isValid()) {
503
0
      return false; // overflow detected
504
0
    }
505
0
506
0
    if (!aDest.SetLength(newLen.value(), mozilla::fallible))
507
0
      return false; // out of memory
508
0
    auto writer = aDest.BeginWriting();
509
0
    std::advance(writer, oldLength);
510
0
    nsScannerIterator fromBegin(aSrcStart);
511
0
512
0
    copy_multifragment_string(fromBegin, aSrcEnd, writer);
513
0
    return true;
514
0
  }
515
516
bool
517
FindCharInReadable( char16_t aChar,
518
                    nsScannerIterator& aSearchStart,
519
                    const nsScannerIterator& aSearchEnd )
520
0
  {
521
0
    while ( aSearchStart != aSearchEnd )
522
0
      {
523
0
        int32_t fragmentLength;
524
0
        if ( SameFragment(aSearchStart, aSearchEnd) ) 
525
0
          fragmentLength = aSearchEnd.get() - aSearchStart.get();
526
0
        else
527
0
          fragmentLength = aSearchStart.size_forward();
528
0
529
0
        const char16_t* charFoundAt = nsCharTraits<char16_t>::find(aSearchStart.get(), fragmentLength, aChar);
530
0
        if ( charFoundAt ) {
531
0
          aSearchStart.advance( charFoundAt - aSearchStart.get() );
532
0
          return true;
533
0
        }
534
0
535
0
        aSearchStart.advance(fragmentLength);
536
0
      }
537
0
538
0
    return false;
539
0
  }
540
541
bool
542
FindInReadable( const nsAString& aPattern,
543
                nsScannerIterator& aSearchStart,
544
                nsScannerIterator& aSearchEnd,
545
                const nsStringComparator& compare )
546
0
  {
547
0
    bool found_it = false;
548
0
549
0
      // only bother searching at all if we're given a non-empty range to search
550
0
    if ( aSearchStart != aSearchEnd )
551
0
      {
552
0
        nsAString::const_iterator aPatternStart, aPatternEnd;
553
0
        aPattern.BeginReading(aPatternStart);
554
0
        aPattern.EndReading(aPatternEnd);
555
0
556
0
          // outer loop keeps searching till we find it or run out of string to search
557
0
        while ( !found_it )
558
0
          {
559
0
              // fast inner loop (that's what it's called, not what it is) looks for a potential match
560
0
            while ( aSearchStart != aSearchEnd &&
561
0
                    compare(aPatternStart.get(), aSearchStart.get(), 1, 1) )
562
0
              ++aSearchStart;
563
0
564
0
              // if we broke out of the `fast' loop because we're out of string ... we're done: no match
565
0
            if ( aSearchStart == aSearchEnd )
566
0
              break;
567
0
568
0
              // otherwise, we're at a potential match, let's see if we really hit one
569
0
            nsAString::const_iterator testPattern(aPatternStart);
570
0
            nsScannerIterator testSearch(aSearchStart);
571
0
572
0
              // slow inner loop verifies the potential match (found by the `fast' loop) at the current position
573
0
            for(;;)
574
0
              {
575
0
                  // we already compared the first character in the outer loop,
576
0
                  //  so we'll advance before the next comparison
577
0
                ++testPattern;
578
0
                ++testSearch;
579
0
580
0
                  // if we verified all the way to the end of the pattern, then we found it!
581
0
                if ( testPattern == aPatternEnd )
582
0
                  {
583
0
                    found_it = true;
584
0
                    aSearchEnd = testSearch; // return the exact found range through the parameters
585
0
                    break;
586
0
                  }
587
0
588
0
                  // if we got to end of the string we're searching before we hit the end of the
589
0
                  //  pattern, we'll never find what we're looking for
590
0
                if ( testSearch == aSearchEnd )
591
0
                  {
592
0
                    aSearchStart = aSearchEnd;
593
0
                    break;
594
0
                  }
595
0
596
0
                  // else if we mismatched ... it's time to advance to the next search position
597
0
                  //  and get back into the `fast' loop
598
0
                if ( compare(testPattern.get(), testSearch.get(), 1, 1) )
599
0
                  {
600
0
                    ++aSearchStart;
601
0
                    break;
602
0
                  }
603
0
              }
604
0
          }
605
0
      }
606
0
607
0
    return found_it;
608
0
  }
609
610
  /**
611
   * This implementation is simple, but does too much work.
612
   * It searches the entire string from left to right, and returns the last match found, if any.
613
   * This implementation will be replaced when I get |reverse_iterator|s working.
614
   */
615
bool
616
RFindInReadable( const nsAString& aPattern,
617
                 nsScannerIterator& aSearchStart,
618
                 nsScannerIterator& aSearchEnd,
619
                 const nsStringComparator& aComparator )
620
0
  {
621
0
    bool found_it = false;
622
0
623
0
    nsScannerIterator savedSearchEnd(aSearchEnd);
624
0
    nsScannerIterator searchStart(aSearchStart), searchEnd(aSearchEnd);
625
0
626
0
    while ( searchStart != searchEnd )
627
0
      {
628
0
        if ( FindInReadable(aPattern, searchStart, searchEnd, aComparator) )
629
0
          {
630
0
            found_it = true;
631
0
632
0
              // this is the best match so far, so remember it
633
0
            aSearchStart = searchStart;
634
0
            aSearchEnd = searchEnd;
635
0
636
0
              // ...and get ready to search some more
637
0
              //  (it's tempting to set |searchStart=searchEnd| ... but that misses overlapping patterns)
638
0
            ++searchStart;
639
0
            searchEnd = savedSearchEnd;
640
0
          }
641
0
      }
642
0
643
0
      // if we never found it, return an empty range
644
0
    if ( !found_it )
645
0
      aSearchStart = aSearchEnd;
646
0
647
0
    return found_it;
648
0
  }