Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/nsTextFragment.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
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
/*
8
 * A class which represents a fragment of text (eg inside a text
9
 * node); if only codepoints below 256 are used, the text is stored as
10
 * a char*; otherwise the text is stored as a char16_t*
11
 */
12
13
#include "nsTextFragment.h"
14
#include "nsCRT.h"
15
#include "nsReadableUtils.h"
16
#include "nsMemory.h"
17
#include "nsBidiUtils.h"
18
#include "nsUnicharUtils.h"
19
#include "mozilla/CheckedInt.h"
20
#include "mozilla/MemoryReporting.h"
21
#include "mozilla/SSE.h"
22
#include "nsTextFragmentImpl.h"
23
#include <algorithm>
24
25
1.27k
#define TEXTFRAG_WHITE_AFTER_NEWLINE 50
26
27
#define TEXTFRAG_MAX_NEWLINES 7
27
28
// Static buffer used for common fragments
29
static char* sSpaceSharedString[TEXTFRAG_MAX_NEWLINES + 1];
30
static char* sTabSharedString[TEXTFRAG_MAX_NEWLINES + 1];
31
static char sSingleCharSharedString[256];
32
33
using namespace mozilla;
34
35
// static
36
nsresult
37
nsTextFragment::Init()
38
3
{
39
3
  // Create whitespace strings
40
3
  uint32_t i;
41
27
  for (i = 0; i <= TEXTFRAG_MAX_NEWLINES; ++i) {
42
24
    sSpaceSharedString[i] = new char[1 + i + TEXTFRAG_WHITE_AFTER_NEWLINE];
43
24
    sTabSharedString[i] = new char[1 + i + TEXTFRAG_WHITE_AFTER_NEWLINE];
44
24
    sSpaceSharedString[i][0] = ' ';
45
24
    sTabSharedString[i][0] = ' ';
46
24
    uint32_t j;
47
108
    for (j = 1; j < 1 + i; ++j) {
48
84
      sSpaceSharedString[i][j] = '\n';
49
84
      sTabSharedString[i][j] = '\n';
50
84
    }
51
1.22k
    for (; j < (1 + i + TEXTFRAG_WHITE_AFTER_NEWLINE); ++j) {
52
1.20k
      sSpaceSharedString[i][j] = ' ';
53
1.20k
      sTabSharedString[i][j] = '\t';
54
1.20k
    }
55
24
  }
56
3
57
3
  // Create single-char strings
58
771
  for (i = 0; i < 256; ++i) {
59
768
    sSingleCharSharedString[i] = i;
60
768
  }
61
3
62
3
  return NS_OK;
63
3
}
64
65
// static
66
void
67
nsTextFragment::Shutdown()
68
0
{
69
0
  uint32_t  i;
70
0
  for (i = 0; i <= TEXTFRAG_MAX_NEWLINES; ++i) {
71
0
    delete [] sSpaceSharedString[i];
72
0
    delete [] sTabSharedString[i];
73
0
    sSpaceSharedString[i] = nullptr;
74
0
    sTabSharedString[i] = nullptr;
75
0
  }
76
0
}
77
78
nsTextFragment::~nsTextFragment()
79
0
{
80
0
  ReleaseText();
81
0
  MOZ_COUNT_DTOR(nsTextFragment);
82
0
}
83
84
void
85
nsTextFragment::ReleaseText()
86
0
{
87
0
  if (mState.mIs2b) {
88
0
    NS_RELEASE(m2b);
89
0
  } else if (mState.mLength && m1b && mState.mInHeap) {
90
0
    free(const_cast<char*>(m1b));
91
0
  }
92
0
93
0
  m1b = nullptr;
94
0
  mState.mIsBidi = false;
95
0
96
0
  // Set mState.mIs2b, mState.mInHeap, and mState.mLength = 0 with mAllBits;
97
0
  mAllBits = 0;
98
0
}
99
100
nsTextFragment&
101
nsTextFragment::operator=(const nsTextFragment& aOther)
102
0
{
103
0
  ReleaseText();
104
0
105
0
  if (aOther.mState.mLength) {
106
0
    if (!aOther.mState.mInHeap) {
107
0
      MOZ_ASSERT(!aOther.mState.mIs2b);
108
0
      m1b = aOther.m1b;
109
0
    } else if (aOther.mState.mIs2b) {
110
0
      m2b = aOther.m2b;
111
0
      NS_ADDREF(m2b);
112
0
    } else {
113
0
      m1b = static_cast<char*>(malloc(aOther.mState.mLength));
114
0
      if (m1b) {
115
0
        memcpy(const_cast<char*>(m1b), aOther.m1b, aOther.mState.mLength);
116
0
      } else {
117
0
        // allocate a buffer for a single REPLACEMENT CHARACTER
118
0
        m2b = nsStringBuffer::Alloc(sizeof(char16_t) * 2).take();
119
0
        if (!m2b) {
120
0
          MOZ_CRASH("OOM!");
121
0
        }
122
0
        char16_t* data = static_cast<char16_t*>(m2b->Data());
123
0
        data[0] = 0xFFFD; // REPLACEMENT CHARACTER
124
0
        data[1] = char16_t(0);
125
0
        mState.mIs2b = true;
126
0
        mState.mInHeap = true;
127
0
        mState.mLength = 1;
128
0
        return *this;
129
0
      }
130
0
    }
131
0
132
0
    mAllBits = aOther.mAllBits;
133
0
  }
134
0
135
0
  return *this;
136
0
}
137
138
static inline int32_t
139
FirstNon8BitUnvectorized(const char16_t *str, const char16_t *end)
140
0
{
141
0
  typedef Non8BitParameters<sizeof(size_t)> p;
142
0
  const size_t mask = p::mask();
143
0
  const uint32_t alignMask = p::alignMask();
144
0
  const uint32_t numUnicharsPerWord = p::numUnicharsPerWord();
145
0
  const int32_t len = end - str;
146
0
  int32_t i = 0;
147
0
148
0
  // Align ourselves to a word boundary.
149
0
  int32_t alignLen =
150
0
    std::min(len, int32_t(((-NS_PTR_TO_INT32(str)) & alignMask) / sizeof(char16_t)));
151
0
  for (; i < alignLen; i++) {
152
0
    if (str[i] > 255)
153
0
      return i;
154
0
  }
155
0
156
0
  // Check one word at a time.
157
0
  const int32_t wordWalkEnd = ((len - i) / numUnicharsPerWord) * numUnicharsPerWord;
158
0
  for (; i < wordWalkEnd; i += numUnicharsPerWord) {
159
0
    const size_t word = *reinterpret_cast<const size_t*>(str + i);
160
0
    if (word & mask)
161
0
      return i;
162
0
  }
163
0
164
0
  // Take care of the remainder one character at a time.
165
0
  for (; i < len; i++) {
166
0
    if (str[i] > 255)
167
0
      return i;
168
0
  }
169
0
170
0
  return -1;
171
0
}
172
173
#ifdef MOZILLA_MAY_SUPPORT_SSE2
174
namespace mozilla {
175
  namespace SSE2 {
176
    int32_t FirstNon8Bit(const char16_t *str, const char16_t *end);
177
  } // namespace SSE2
178
} // namespace mozilla
179
#endif
180
181
/*
182
 * This function returns -1 if all characters in str are 8 bit characters.
183
 * Otherwise, it returns a value less than or equal to the index of the first
184
 * non-8bit character in str. For example, if first non-8bit character is at
185
 * position 25, it may return 25, or for example 24, or 16. But it guarantees
186
 * there is no non-8bit character before returned value.
187
 */
188
static inline int32_t
189
FirstNon8Bit(const char16_t *str, const char16_t *end)
190
0
{
191
0
#ifdef MOZILLA_MAY_SUPPORT_SSE2
192
0
  if (mozilla::supports_sse2()) {
193
0
    return mozilla::SSE2::FirstNon8Bit(str, end);
194
0
  }
195
0
#endif
196
0
197
0
  return FirstNon8BitUnvectorized(str, end);
198
0
}
199
200
bool
201
nsTextFragment::SetTo(const char16_t* aBuffer, int32_t aLength,
202
                      bool aUpdateBidi, bool aForce2b)
203
0
{
204
0
  if (aForce2b && mState.mIs2b && !m2b->IsReadonly()) {
205
0
    uint32_t storageSize = m2b->StorageSize();
206
0
    uint32_t neededSize = aLength * sizeof(char16_t);
207
0
    if (!neededSize) {
208
0
      if (storageSize < AutoStringDefaultStorageSize) {
209
0
        // If we're storing small enough nsStringBuffer, let's preserve it.
210
0
211
0
        static_cast<char16_t*>(m2b->Data())[0] = char16_t(0);
212
0
        mState.mLength = 0;
213
0
        mState.mIsBidi = false;
214
0
        return true;
215
0
      }
216
0
    } else if ((neededSize < storageSize) &&
217
0
               ((storageSize / 2) <
218
0
                (neededSize + AutoStringDefaultStorageSize))) {
219
0
      // Don't try to reuse the existing nsStringBuffer, if it would have
220
0
      // lots of unused space.
221
0
222
0
      memcpy(m2b->Data(), aBuffer, neededSize);
223
0
      static_cast<char16_t*>(m2b->Data())[aLength] = char16_t(0);
224
0
      mState.mLength = aLength;
225
0
      mState.mIsBidi = false;
226
0
      if (aUpdateBidi) {
227
0
        UpdateBidiFlag(aBuffer, aLength);
228
0
      }
229
0
      return true;
230
0
    }
231
0
  }
232
0
233
0
  ReleaseText();
234
0
235
0
  if (aLength == 0) {
236
0
    return true;
237
0
  }
238
0
239
0
  char16_t firstChar = *aBuffer;
240
0
  if (!aForce2b && aLength == 1 && firstChar < 256) {
241
0
    m1b = sSingleCharSharedString + firstChar;
242
0
    mState.mInHeap = false;
243
0
    mState.mIs2b = false;
244
0
    mState.mLength = 1;
245
0
246
0
    return true;
247
0
  }
248
0
249
0
  const char16_t *ucp = aBuffer;
250
0
  const char16_t *uend = aBuffer + aLength;
251
0
252
0
  // Check if we can use a shared string
253
0
  if (!aForce2b &&
254
0
      aLength <= 1 + TEXTFRAG_WHITE_AFTER_NEWLINE + TEXTFRAG_MAX_NEWLINES &&
255
0
     (firstChar == ' ' || firstChar == '\n' || firstChar == '\t')) {
256
0
    if (firstChar == ' ') {
257
0
      ++ucp;
258
0
    }
259
0
260
0
    const char16_t* start = ucp;
261
0
    while (ucp < uend && *ucp == '\n') {
262
0
      ++ucp;
263
0
    }
264
0
    const char16_t* endNewLine = ucp;
265
0
266
0
    char16_t space = ucp < uend && *ucp == '\t' ? '\t' : ' ';
267
0
    while (ucp < uend && *ucp == space) {
268
0
      ++ucp;
269
0
    }
270
0
271
0
    if (ucp == uend &&
272
0
        endNewLine - start <= TEXTFRAG_MAX_NEWLINES &&
273
0
        ucp - endNewLine <= TEXTFRAG_WHITE_AFTER_NEWLINE) {
274
0
      char** strings = space == ' ' ? sSpaceSharedString : sTabSharedString;
275
0
      m1b = strings[endNewLine - start];
276
0
277
0
      // If we didn't find a space in the beginning, skip it now.
278
0
      if (firstChar != ' ') {
279
0
        ++m1b;
280
0
      }
281
0
282
0
      mState.mInHeap = false;
283
0
      mState.mIs2b = false;
284
0
      mState.mLength = aLength;
285
0
286
0
      return true;
287
0
    }
288
0
  }
289
0
290
0
  // See if we need to store the data in ucs2 or not
291
0
  int32_t first16bit = aForce2b ? 0 : FirstNon8Bit(ucp, uend);
292
0
293
0
  if (first16bit != -1) { // aBuffer contains no non-8bit character
294
0
    // Use ucs2 storage because we have to
295
0
    CheckedUint32 m2bSize = aLength + 1;
296
0
    m2bSize *= sizeof(char16_t);
297
0
    if (!m2bSize.isValid()) {
298
0
      return false;
299
0
    }
300
0
301
0
    m2b = nsStringBuffer::Alloc(m2bSize.value()).take();
302
0
    if (!m2b) {
303
0
      return false;
304
0
    }
305
0
    memcpy(m2b->Data(), aBuffer, aLength * sizeof(char16_t));
306
0
    static_cast<char16_t*>(m2b->Data())[aLength] = char16_t(0);
307
0
308
0
    mState.mIs2b = true;
309
0
    if (aUpdateBidi) {
310
0
      UpdateBidiFlag(aBuffer + first16bit, aLength - first16bit);
311
0
    }
312
0
313
0
  } else {
314
0
    // Use 1 byte storage because we can
315
0
    char* buff = static_cast<char*>(malloc(aLength));
316
0
    if (!buff) {
317
0
      return false;
318
0
    }
319
0
320
0
    // Copy data
321
0
    LossyConvertUTF16toLatin1(MakeSpan(aBuffer, aLength),
322
0
                              MakeSpan(buff, aLength));
323
0
    m1b = buff;
324
0
    mState.mIs2b = false;
325
0
  }
326
0
327
0
  // Setup our fields
328
0
  mState.mInHeap = true;
329
0
  mState.mLength = aLength;
330
0
331
0
  return true;
332
0
}
333
334
void
335
nsTextFragment::CopyTo(char16_t *aDest, int32_t aOffset, int32_t aCount)
336
0
{
337
0
  NS_ASSERTION(aOffset >= 0, "Bad offset passed to nsTextFragment::CopyTo()!");
338
0
  NS_ASSERTION(aCount >= 0, "Bad count passed to nsTextFragment::CopyTo()!");
339
0
340
0
  if (aOffset < 0) {
341
0
    aOffset = 0;
342
0
  }
343
0
344
0
  if (uint32_t(aOffset + aCount) > GetLength()) {
345
0
    aCount = mState.mLength - aOffset;
346
0
  }
347
0
348
0
  if (aCount != 0) {
349
0
    if (mState.mIs2b) {
350
0
      memcpy(aDest, Get2b() + aOffset, sizeof(char16_t) * aCount);
351
0
    } else {
352
0
      const char *cp = m1b + aOffset;
353
0
      ConvertLatin1toUTF16(MakeSpan(cp, aCount), MakeSpan(aDest, aCount));
354
0
    }
355
0
  }
356
0
}
357
358
bool
359
nsTextFragment::Append(const char16_t* aBuffer, uint32_t aLength,
360
                       bool aUpdateBidi, bool aForce2b)
361
0
{
362
0
  if (!aLength) {
363
0
    return true;
364
0
  }
365
0
366
0
  // This is a common case because some callsites create a textnode
367
0
  // with a value by creating the node and then calling AppendData.
368
0
  if (mState.mLength == 0) {
369
0
    return SetTo(aBuffer, aLength, aUpdateBidi, aForce2b);
370
0
  }
371
0
372
0
  // Should we optimize for aData.Length() == 0?
373
0
374
0
  // FYI: Don't use CheckedInt in this method since here is very hot path
375
0
  //      in some performance tests.
376
0
  if (NS_MAX_TEXT_FRAGMENT_LENGTH - mState.mLength < aLength) {
377
0
    return false;  // Would be overflown if we'd keep handling.
378
0
  }
379
0
380
0
  if (mState.mIs2b) {
381
0
    size_t size = mState.mLength + aLength + 1;
382
0
    if (SIZE_MAX / sizeof(char16_t) < size) {
383
0
      return false;  // Would be overflown if we'd keep handling.
384
0
    }
385
0
    size *= sizeof(char16_t);
386
0
387
0
    // Already a 2-byte string so the result will be too
388
0
    nsStringBuffer* buff = nullptr;
389
0
    nsStringBuffer* bufferToRelease = nullptr;
390
0
    if (m2b->IsReadonly()) {
391
0
      buff = nsStringBuffer::Alloc(size).take();
392
0
      if (!buff) {
393
0
        return false;
394
0
      }
395
0
      bufferToRelease = m2b;
396
0
      memcpy(static_cast<char16_t*>(buff->Data()), m2b->Data(),
397
0
             mState.mLength * sizeof(char16_t));
398
0
    } else {
399
0
      buff = nsStringBuffer::Realloc(m2b, size);
400
0
      if (!buff) {
401
0
        return false;
402
0
      }
403
0
    }
404
0
405
0
    char16_t* data = static_cast<char16_t*>(buff->Data());
406
0
    memcpy(data + mState.mLength, aBuffer,
407
0
           aLength * sizeof(char16_t));
408
0
    mState.mLength += aLength;
409
0
    m2b = buff;
410
0
    data[mState.mLength] = char16_t(0);
411
0
412
0
    NS_IF_RELEASE(bufferToRelease);
413
0
414
0
    if (aUpdateBidi) {
415
0
      UpdateBidiFlag(aBuffer, aLength);
416
0
    }
417
0
418
0
    return true;
419
0
  }
420
0
421
0
  // Current string is a 1-byte string, check if the new data fits in one byte too.
422
0
  int32_t first16bit = aForce2b ? 0 : FirstNon8Bit(aBuffer, aBuffer + aLength);
423
0
424
0
  if (first16bit != -1) { // aBuffer contains no non-8bit character
425
0
    size_t size = mState.mLength + aLength + 1;
426
0
    if (SIZE_MAX / sizeof(char16_t) < size) {
427
0
      return false;  // Would be overflown if we'd keep handling.
428
0
    }
429
0
    size *= sizeof(char16_t);
430
0
431
0
    // The old data was 1-byte, but the new is not so we have to expand it
432
0
    // all to 2-byte
433
0
    nsStringBuffer* buff = nsStringBuffer::Alloc(size).take();
434
0
    if (!buff) {
435
0
      return false;
436
0
    }
437
0
438
0
    // Copy data into buff
439
0
    char16_t* data = static_cast<char16_t*>(buff->Data());
440
0
    ConvertLatin1toUTF16(MakeSpan(m1b, mState.mLength),
441
0
                         MakeSpan(data, mState.mLength));
442
0
443
0
    memcpy(data + mState.mLength, aBuffer, aLength * sizeof(char16_t));
444
0
    mState.mLength += aLength;
445
0
    mState.mIs2b = true;
446
0
447
0
    if (mState.mInHeap) {
448
0
      free(const_cast<char*>(m1b));
449
0
    }
450
0
    data[mState.mLength] = char16_t(0);
451
0
    m2b = buff;
452
0
453
0
    mState.mInHeap = true;
454
0
455
0
    if (aUpdateBidi) {
456
0
      UpdateBidiFlag(aBuffer + first16bit, aLength - first16bit);
457
0
    }
458
0
459
0
    return true;
460
0
  }
461
0
462
0
  // The new and the old data is all 1-byte
463
0
  size_t size = mState.mLength + aLength;
464
0
  MOZ_ASSERT(sizeof(char) == 1);
465
0
  char* buff;
466
0
  if (mState.mInHeap) {
467
0
    buff = static_cast<char*>(realloc(const_cast<char*>(m1b), size));
468
0
    if (!buff) {
469
0
      return false;
470
0
    }
471
0
  }
472
0
  else {
473
0
    buff = static_cast<char*>(malloc(size));
474
0
    if (!buff) {
475
0
      return false;
476
0
    }
477
0
478
0
    memcpy(buff, m1b, mState.mLength);
479
0
    mState.mInHeap = true;
480
0
  }
481
0
482
0
  // Copy aBuffer into buff.
483
0
  LossyConvertUTF16toLatin1(MakeSpan(aBuffer, aLength),
484
0
                            MakeSpan(buff + mState.mLength, aLength));
485
0
486
0
  m1b = buff;
487
0
  mState.mLength += aLength;
488
0
489
0
  return true;
490
0
}
491
492
/* virtual */ size_t
493
nsTextFragment::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
494
0
{
495
0
  if (Is2b()) {
496
0
    return m2b->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
497
0
  }
498
0
499
0
  if (mState.mInHeap) {
500
0
    return aMallocSizeOf(m1b);
501
0
  }
502
0
503
0
  return 0;
504
0
}
505
506
// To save time we only do this when we really want to know, not during
507
// every allocation
508
void
509
nsTextFragment::UpdateBidiFlag(const char16_t* aBuffer, uint32_t aLength)
510
0
{
511
0
  if (mState.mIs2b && !mState.mIsBidi) {
512
0
    if (HasRTLChars(MakeSpan(aBuffer, aLength))) {
513
0
      mState.mIsBidi = true;
514
0
    }
515
0
  }
516
0
}