Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
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 : // JS lexical scanner.
8 :
9 : #include "frontend/TokenStream.h"
10 :
11 : #include "mozilla/ArrayUtils.h"
12 : #include "mozilla/IntegerTypeTraits.h"
13 : #include "mozilla/PodOperations.h"
14 :
15 : #include <ctype.h>
16 : #include <stdarg.h>
17 : #include <stdio.h>
18 : #include <string.h>
19 :
20 : #include "jsatom.h"
21 : #include "jscntxt.h"
22 : #include "jscompartment.h"
23 : #include "jsexn.h"
24 : #include "jsnum.h"
25 :
26 : #include "frontend/BytecodeCompiler.h"
27 : #include "frontend/ReservedWords.h"
28 : #include "js/CharacterEncoding.h"
29 : #include "js/UniquePtr.h"
30 : #include "vm/HelperThreads.h"
31 : #include "vm/StringBuffer.h"
32 : #include "vm/Unicode.h"
33 :
34 : using mozilla::ArrayLength;
35 : using mozilla::Maybe;
36 : using mozilla::PodAssign;
37 : using mozilla::PodCopy;
38 : using mozilla::PodZero;
39 :
40 : struct ReservedWordInfo
41 : {
42 : const char* chars; // C string with reserved word text
43 : js::frontend::TokenKind tokentype;
44 : };
45 :
46 : static const ReservedWordInfo reservedWords[] = {
47 : #define RESERVED_WORD_INFO(word, name, type) \
48 : {js_##word##_str, js::frontend::type},
49 : FOR_EACH_JAVASCRIPT_RESERVED_WORD(RESERVED_WORD_INFO)
50 : #undef RESERVED_WORD_INFO
51 : };
52 :
53 : // Returns a ReservedWordInfo for the specified characters, or nullptr if the
54 : // string is not a reserved word.
55 : template <typename CharT>
56 : static const ReservedWordInfo*
57 319048 : FindReservedWord(const CharT* s, size_t length)
58 : {
59 319048 : MOZ_ASSERT(length != 0);
60 :
61 : size_t i;
62 : const ReservedWordInfo* rw;
63 : const char* chars;
64 :
65 : #define JSRW_LENGTH() length
66 : #define JSRW_AT(column) s[column]
67 : #define JSRW_GOT_MATCH(index) i = (index); goto got_match;
68 : #define JSRW_TEST_GUESS(index) i = (index); goto test_guess;
69 : #define JSRW_NO_MATCH() goto no_match;
70 : #include "frontend/ReservedWordsGenerated.h"
71 : #undef JSRW_NO_MATCH
72 : #undef JSRW_TEST_GUESS
73 : #undef JSRW_GOT_MATCH
74 : #undef JSRW_AT
75 : #undef JSRW_LENGTH
76 :
77 : got_match:
78 49847 : return &reservedWords[i];
79 :
80 : test_guess:
81 24218 : rw = &reservedWords[i];
82 24218 : chars = rw->chars;
83 100306 : do {
84 112871 : if (*s++ != (unsigned char)(*chars++))
85 12565 : goto no_match;
86 : } while (--length != 0);
87 11653 : return rw;
88 :
89 : no_match:
90 257548 : return nullptr;
91 : }
92 :
93 : static const ReservedWordInfo*
94 106021 : FindReservedWord(JSLinearString* str)
95 : {
96 212042 : JS::AutoCheckCannotGC nogc;
97 106021 : return str->hasLatin1Chars()
98 106021 : ? FindReservedWord(str->latin1Chars(nogc), str->length())
99 212042 : : FindReservedWord(str->twoByteChars(nogc), str->length());
100 : }
101 :
102 : template <typename CharT>
103 : static bool
104 192 : IsIdentifier(const CharT* chars, size_t length)
105 : {
106 : using namespace js;
107 :
108 192 : if (length == 0)
109 0 : return false;
110 :
111 192 : if (!unicode::IsIdentifierStart(char16_t(*chars)))
112 1 : return false;
113 :
114 191 : const CharT* end = chars + length;
115 3233 : while (++chars != end) {
116 1521 : if (!unicode::IsIdentifierPart(char16_t(*chars)))
117 0 : return false;
118 : }
119 :
120 191 : return true;
121 : }
122 :
123 : static uint32_t
124 0 : GetSingleCodePoint(const char16_t** p, const char16_t* end)
125 : {
126 : using namespace js;
127 :
128 : uint32_t codePoint;
129 0 : if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(**p)) && *p + 1 < end) {
130 0 : char16_t lead = **p;
131 0 : char16_t maybeTrail = *(*p + 1);
132 0 : if (unicode::IsTrailSurrogate(maybeTrail)) {
133 0 : *p += 2;
134 0 : return unicode::UTF16Decode(lead, maybeTrail);
135 : }
136 : }
137 :
138 0 : codePoint = **p;
139 0 : (*p)++;
140 0 : return codePoint;
141 : }
142 :
143 : static bool
144 0 : IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length)
145 : {
146 : using namespace js;
147 :
148 0 : if (IsIdentifier(chars, length))
149 0 : return true;
150 :
151 0 : if (length == 0)
152 0 : return false;
153 :
154 0 : const char16_t* p = chars;
155 0 : const char16_t* end = chars + length;
156 : uint32_t codePoint;
157 :
158 0 : codePoint = GetSingleCodePoint(&p, end);
159 0 : if (!unicode::IsIdentifierStart(codePoint))
160 0 : return false;
161 :
162 0 : while (p < end) {
163 0 : codePoint = GetSingleCodePoint(&p, end);
164 0 : if (!unicode::IsIdentifierPart(codePoint))
165 0 : return false;
166 : }
167 :
168 0 : return true;
169 : }
170 :
171 : namespace js {
172 :
173 : namespace frontend {
174 :
175 : bool
176 181 : IsIdentifier(JSLinearString* str)
177 : {
178 362 : JS::AutoCheckCannotGC nogc;
179 181 : return str->hasLatin1Chars()
180 181 : ? ::IsIdentifier(str->latin1Chars(nogc), str->length())
181 362 : : ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length());
182 : }
183 :
184 : bool
185 11 : IsIdentifier(const char* chars, size_t length)
186 : {
187 11 : return ::IsIdentifier(chars, length);
188 : }
189 :
190 : bool
191 0 : IsIdentifier(const char16_t* chars, size_t length)
192 : {
193 0 : return ::IsIdentifier(chars, length);
194 : }
195 :
196 : bool
197 0 : IsKeyword(JSLinearString* str)
198 : {
199 0 : if (const ReservedWordInfo* rw = FindReservedWord(str))
200 0 : return TokenKindIsKeyword(rw->tokentype);
201 :
202 0 : return false;
203 : }
204 :
205 : TokenKind
206 106021 : ReservedWordTokenKind(PropertyName* str)
207 : {
208 106021 : if (const ReservedWordInfo* rw = FindReservedWord(str))
209 656 : return rw->tokentype;
210 :
211 105365 : return TOK_NAME;
212 : }
213 :
214 : const char*
215 0 : ReservedWordToCharZ(PropertyName* str)
216 : {
217 0 : if (const ReservedWordInfo* rw = FindReservedWord(str))
218 0 : return ReservedWordToCharZ(rw->tokentype);
219 :
220 0 : return nullptr;
221 : }
222 :
223 : const char*
224 0 : ReservedWordToCharZ(TokenKind tt)
225 : {
226 0 : MOZ_ASSERT(tt != TOK_NAME);
227 0 : switch (tt) {
228 : #define EMIT_CASE(word, name, type) case type: return js_##word##_str;
229 0 : FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE)
230 : #undef EMIT_CASE
231 : default:
232 0 : MOZ_ASSERT_UNREACHABLE("Not a reserved word PropertyName.");
233 : }
234 : return nullptr;
235 : }
236 :
237 : PropertyName*
238 2599 : TokenStreamAnyChars::reservedWordToPropertyName(TokenKind tt) const
239 : {
240 2599 : MOZ_ASSERT(tt != TOK_NAME);
241 2599 : switch (tt) {
242 : #define EMIT_CASE(word, name, type) case type: return cx->names().name;
243 0 : FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE)
244 : #undef EMIT_CASE
245 : default:
246 0 : MOZ_ASSERT_UNREACHABLE("Not a reserved word TokenKind.");
247 : }
248 : return nullptr;
249 : }
250 :
251 2449 : TokenStream::SourceCoords::SourceCoords(JSContext* cx, uint32_t ln)
252 2449 : : lineStartOffsets_(cx), initialLineNum_(ln), lastLineIndex_(0)
253 : {
254 : // This is actually necessary! Removing it causes compile errors on
255 : // GCC and clang. You could try declaring this:
256 : //
257 : // const uint32_t TokenStream::SourceCoords::MAX_PTR;
258 : //
259 : // which fixes the GCC/clang error, but causes bustage on Windows. Sigh.
260 : //
261 2449 : uint32_t maxPtr = MAX_PTR;
262 :
263 : // The first line begins at buffer offset 0. MAX_PTR is the sentinel. The
264 : // appends cannot fail because |lineStartOffsets_| has statically-allocated
265 : // elements.
266 2449 : MOZ_ASSERT(lineStartOffsets_.capacity() >= 2);
267 2449 : MOZ_ALWAYS_TRUE(lineStartOffsets_.reserve(2));
268 2449 : lineStartOffsets_.infallibleAppend(0);
269 2449 : lineStartOffsets_.infallibleAppend(maxPtr);
270 2449 : }
271 :
272 : MOZ_ALWAYS_INLINE bool
273 124078 : TokenStream::SourceCoords::add(uint32_t lineNum, uint32_t lineStartOffset)
274 : {
275 124078 : uint32_t lineIndex = lineNumToIndex(lineNum);
276 124078 : uint32_t sentinelIndex = lineStartOffsets_.length() - 1;
277 :
278 124078 : MOZ_ASSERT(lineStartOffsets_[0] == 0 && lineStartOffsets_[sentinelIndex] == MAX_PTR);
279 :
280 124078 : if (lineIndex == sentinelIndex) {
281 : // We haven't seen this newline before. Update lineStartOffsets_
282 : // only if lineStartOffsets_.append succeeds, to keep sentinel.
283 : // Otherwise return false to tell TokenStream about OOM.
284 116543 : uint32_t maxPtr = MAX_PTR;
285 116543 : if (!lineStartOffsets_.append(maxPtr)) {
286 : static_assert(mozilla::IsSame<decltype(lineStartOffsets_.allocPolicy()),
287 : TempAllocPolicy&>::value,
288 : "this function's caller depends on it reporting an "
289 : "error on failure, as TempAllocPolicy ensures");
290 0 : return false;
291 : }
292 :
293 116543 : lineStartOffsets_[lineIndex] = lineStartOffset;
294 : } else {
295 : // We have seen this newline before (and ungot it). Do nothing (other
296 : // than checking it hasn't mysteriously changed).
297 : // This path can be executed after hitting OOM, so check lineIndex.
298 7535 : MOZ_ASSERT_IF(lineIndex < sentinelIndex, lineStartOffsets_[lineIndex] == lineStartOffset);
299 : }
300 124078 : return true;
301 : }
302 :
303 : MOZ_ALWAYS_INLINE bool
304 3102 : TokenStreamAnyChars::SourceCoords::fill(const TokenStreamAnyChars::SourceCoords& other)
305 : {
306 3102 : MOZ_ASSERT(lineStartOffsets_.back() == MAX_PTR);
307 3102 : MOZ_ASSERT(other.lineStartOffsets_.back() == MAX_PTR);
308 :
309 3102 : if (lineStartOffsets_.length() >= other.lineStartOffsets_.length())
310 50 : return true;
311 :
312 3052 : uint32_t sentinelIndex = lineStartOffsets_.length() - 1;
313 3052 : lineStartOffsets_[sentinelIndex] = other.lineStartOffsets_[sentinelIndex];
314 :
315 37558 : for (size_t i = sentinelIndex + 1; i < other.lineStartOffsets_.length(); i++) {
316 34506 : if (!lineStartOffsets_.append(other.lineStartOffsets_[i]))
317 0 : return false;
318 : }
319 3052 : return true;
320 : }
321 :
322 : MOZ_ALWAYS_INLINE uint32_t
323 193517 : TokenStreamAnyChars::SourceCoords::lineIndexOf(uint32_t offset) const
324 : {
325 : uint32_t iMin, iMax, iMid;
326 :
327 193517 : if (lineStartOffsets_[lastLineIndex_] <= offset) {
328 : // If we reach here, offset is on a line the same as or higher than
329 : // last time. Check first for the +0, +1, +2 cases, because they
330 : // typically cover 85--98% of cases.
331 183196 : if (offset < lineStartOffsets_[lastLineIndex_ + 1])
332 107484 : return lastLineIndex_; // lineIndex is same as last time
333 :
334 : // If we reach here, there must be at least one more entry (plus the
335 : // sentinel). Try it.
336 75712 : lastLineIndex_++;
337 75712 : if (offset < lineStartOffsets_[lastLineIndex_ + 1])
338 43028 : return lastLineIndex_; // lineIndex is one higher than last time
339 :
340 : // The same logic applies here.
341 32684 : lastLineIndex_++;
342 32684 : if (offset < lineStartOffsets_[lastLineIndex_ + 1]) {
343 9545 : return lastLineIndex_; // lineIndex is two higher than last time
344 : }
345 :
346 : // No luck. Oh well, we have a better-than-default starting point for
347 : // the binary search.
348 23139 : iMin = lastLineIndex_ + 1;
349 23139 : MOZ_ASSERT(iMin < lineStartOffsets_.length() - 1); // -1 due to the sentinel
350 :
351 : } else {
352 10321 : iMin = 0;
353 : }
354 :
355 : // This is a binary search with deferred detection of equality, which was
356 : // marginally faster in this case than a standard binary search.
357 : // The -2 is because |lineStartOffsets_.length() - 1| is the sentinel, and we
358 : // want one before that.
359 33460 : iMax = lineStartOffsets_.length() - 2;
360 508294 : while (iMax > iMin) {
361 237417 : iMid = iMin + (iMax - iMin) / 2;
362 237417 : if (offset >= lineStartOffsets_[iMid + 1])
363 88065 : iMin = iMid + 1; // offset is above lineStartOffsets_[iMid]
364 : else
365 149352 : iMax = iMid; // offset is below or within lineStartOffsets_[iMid]
366 : }
367 33460 : MOZ_ASSERT(iMax == iMin);
368 33460 : MOZ_ASSERT(lineStartOffsets_[iMin] <= offset && offset < lineStartOffsets_[iMin + 1]);
369 33460 : lastLineIndex_ = iMin;
370 33460 : return iMin;
371 : }
372 :
373 : uint32_t
374 94385 : TokenStreamAnyChars::SourceCoords::lineNum(uint32_t offset) const
375 : {
376 94385 : uint32_t lineIndex = lineIndexOf(offset);
377 94385 : return lineIndexToNum(lineIndex);
378 : }
379 :
380 : uint32_t
381 85525 : TokenStreamAnyChars::SourceCoords::columnIndex(uint32_t offset) const
382 : {
383 85525 : uint32_t lineIndex = lineIndexOf(offset);
384 85525 : uint32_t lineStartOffset = lineStartOffsets_[lineIndex];
385 85525 : MOZ_ASSERT(offset >= lineStartOffset);
386 85525 : return offset - lineStartOffset;
387 : }
388 :
389 : void
390 13607 : TokenStreamAnyChars::SourceCoords::lineNumAndColumnIndex(uint32_t offset, uint32_t* lineNum,
391 : uint32_t* columnIndex) const
392 : {
393 13607 : uint32_t lineIndex = lineIndexOf(offset);
394 13607 : *lineNum = lineIndexToNum(lineIndex);
395 13607 : uint32_t lineStartOffset = lineStartOffsets_[lineIndex];
396 13607 : MOZ_ASSERT(offset >= lineStartOffset);
397 13607 : *columnIndex = offset - lineStartOffset;
398 13607 : }
399 :
400 : #ifdef _MSC_VER
401 : #pragma warning(push)
402 : #pragma warning(disable:4351)
403 : #endif
404 :
405 2449 : TokenStreamAnyChars::TokenStreamAnyChars(JSContext* cx, const ReadOnlyCompileOptions& options,
406 2449 : StrictModeGetter* smg)
407 2449 : : srcCoords(cx, options.lineno),
408 : options_(options),
409 : tokens(),
410 : cursor(),
411 : lookahead(),
412 2449 : lineno(options.lineno),
413 : flags(),
414 : linebase(0),
415 : prevLinebase(size_t(-1)),
416 2449 : filename(options.filename()),
417 : displayURL_(nullptr),
418 : sourceMapURL_(nullptr),
419 : cx(cx),
420 2449 : mutedErrors(options.mutedErrors()),
421 12245 : strictModeGetter(smg)
422 : {
423 2449 : }
424 :
425 2449 : TokenStream::TokenStream(JSContext* cx, const ReadOnlyCompileOptions& options,
426 2449 : const CharT* base, size_t length, StrictModeGetter* smg)
427 : : TokenStreamAnyChars(cx, options, smg),
428 2449 : userbuf(cx, base, length, options.column),
429 4898 : tokenbuf(cx)
430 : {
431 : // Nb: the following tables could be static, but initializing them here is
432 : // much easier. Don't worry, the time to initialize them for each
433 : // TokenStream is trivial. See bug 639420.
434 :
435 : // See Parser::assignExpr() for an explanation of isExprEnding[].
436 2449 : memset(isExprEnding, 0, sizeof(isExprEnding));
437 2449 : isExprEnding[TOK_COMMA] = 1;
438 2449 : isExprEnding[TOK_SEMI] = 1;
439 2449 : isExprEnding[TOK_COLON] = 1;
440 2449 : isExprEnding[TOK_RP] = 1;
441 2449 : isExprEnding[TOK_RB] = 1;
442 2449 : isExprEnding[TOK_RC] = 1;
443 2449 : }
444 :
445 : #ifdef _MSC_VER
446 : #pragma warning(pop)
447 : #endif
448 :
449 : bool
450 1902 : TokenStreamAnyChars::checkOptions()
451 : {
452 : // Constrain starting columns to half of the range of a signed 32-bit value,
453 : // to avoid overflow.
454 1902 : if (options().column >= mozilla::MaxValue<int32_t>::value / 2 + 1) {
455 0 : reportErrorNoOffset(JSMSG_BAD_COLUMN_NUMBER);
456 0 : return false;
457 : }
458 :
459 1902 : return true;
460 : }
461 :
462 : // Use the fastest available getc.
463 : #if defined(HAVE_GETC_UNLOCKED)
464 : # define fast_getc getc_unlocked
465 : #elif defined(HAVE__GETC_NOLOCK)
466 : # define fast_getc _getc_nolock
467 : #else
468 : # define fast_getc getc
469 : #endif
470 :
471 : MOZ_MUST_USE MOZ_ALWAYS_INLINE bool
472 124078 : TokenStream::updateLineInfoForEOL()
473 : {
474 124078 : prevLinebase = linebase;
475 124078 : linebase = userbuf.offset();
476 124078 : lineno++;
477 124078 : return srcCoords.add(lineno, linebase);
478 : }
479 :
480 : MOZ_ALWAYS_INLINE void
481 104629 : TokenStreamAnyChars::updateFlagsForEOL()
482 : {
483 104629 : flags.isDirtyLine = false;
484 104629 : }
485 :
486 : // This gets the next char, normalizing all EOL sequences to '\n' as it goes.
487 : bool
488 1012579 : TokenStream::getChar(int32_t* cp)
489 : {
490 1012579 : if (MOZ_UNLIKELY(!userbuf.hasRawChars())) {
491 0 : flags.isEOF = true;
492 0 : *cp = EOF;
493 0 : return true;
494 : }
495 :
496 1012579 : int32_t c = userbuf.getRawChar();
497 :
498 : do {
499 : // Normalize the char16_t if it was a newline.
500 1012579 : if (MOZ_UNLIKELY(c == '\n'))
501 21368 : break;
502 :
503 991211 : if (MOZ_UNLIKELY(c == '\r')) {
504 : // If it's a \r\n sequence: treat as a single EOL, skip over the \n.
505 0 : if (MOZ_LIKELY(userbuf.hasRawChars()))
506 0 : userbuf.matchRawChar('\n');
507 :
508 0 : break;
509 : }
510 :
511 991211 : if (MOZ_UNLIKELY(c == unicode::LINE_SEPARATOR || c == unicode::PARA_SEPARATOR))
512 : break;
513 :
514 991211 : *cp = c;
515 991211 : return true;
516 : } while (false);
517 :
518 21368 : if (!updateLineInfoForEOL())
519 0 : return false;
520 :
521 21368 : *cp = '\n';
522 21368 : return true;
523 : }
524 :
525 : // This gets the next char. It does nothing special with EOL sequences, not
526 : // even updating the line counters. It can be used safely if (a) the
527 : // resulting char is guaranteed to be ungotten (by ungetCharIgnoreEOL()) if
528 : // it's an EOL, and (b) the line-related state (lineno, linebase) is not used
529 : // before it's ungotten.
530 : int32_t
531 2112202 : TokenStream::getCharIgnoreEOL()
532 : {
533 2112202 : if (MOZ_LIKELY(userbuf.hasRawChars()))
534 2112095 : return userbuf.getRawChar();
535 :
536 107 : flags.isEOF = true;
537 107 : return EOF;
538 : }
539 :
540 : void
541 15555 : TokenStream::ungetChar(int32_t c)
542 : {
543 15555 : if (c == EOF)
544 0 : return;
545 15555 : MOZ_ASSERT(!userbuf.atStart());
546 15555 : userbuf.ungetRawChar();
547 15555 : if (c == '\n') {
548 : #ifdef DEBUG
549 7478 : int32_t c2 = userbuf.peekRawChar();
550 7478 : MOZ_ASSERT(TokenBuf::isRawEOLChar(c2));
551 : #endif
552 :
553 : // If it's a \r\n sequence, also unget the \r.
554 7478 : if (!userbuf.atStart())
555 7478 : userbuf.matchRawCharBackwards('\r');
556 :
557 7478 : MOZ_ASSERT(prevLinebase != size_t(-1)); // we should never get more than one EOL char
558 7478 : linebase = prevLinebase;
559 7478 : prevLinebase = size_t(-1);
560 7478 : lineno--;
561 : } else {
562 8077 : MOZ_ASSERT(userbuf.peekRawChar() == c);
563 : }
564 : }
565 :
566 : void
567 335614 : TokenStream::ungetCharIgnoreEOL(int32_t c)
568 : {
569 335614 : if (c == EOF)
570 107 : return;
571 335507 : MOZ_ASSERT(!userbuf.atStart());
572 335507 : userbuf.ungetRawChar();
573 : }
574 :
575 : // Return true iff |n| raw characters can be read from this without reading past
576 : // EOF or a newline, and copy those characters into |cp| if so. The characters
577 : // are not consumed: use skipChars(n) to do so after checking that the consumed
578 : // characters had appropriate values.
579 : bool
580 5178 : TokenStream::peekChars(int n, char16_t* cp)
581 : {
582 : int i, j;
583 : int32_t c;
584 :
585 76775 : for (i = 0; i < n; i++) {
586 72313 : c = getCharIgnoreEOL();
587 72313 : if (c == EOF)
588 0 : break;
589 72313 : if (c == '\n') {
590 716 : ungetCharIgnoreEOL(c);
591 716 : break;
592 : }
593 71597 : cp[i] = char16_t(c);
594 : }
595 76775 : for (j = i - 1; j >= 0; j--)
596 71597 : ungetCharIgnoreEOL(cp[j]);
597 5178 : return i == n;
598 : }
599 :
600 : size_t
601 0 : TokenStream::TokenBuf::findEOLMax(size_t start, size_t max)
602 : {
603 0 : const CharT* p = rawCharPtrAt(start);
604 :
605 0 : size_t n = 0;
606 : while (true) {
607 0 : if (p >= limit_)
608 0 : break;
609 0 : if (n >= max)
610 0 : break;
611 0 : n++;
612 0 : if (TokenBuf::isRawEOLChar(*p++))
613 0 : break;
614 : }
615 0 : return start + n;
616 : }
617 :
618 : bool
619 260 : TokenStream::advance(size_t position)
620 : {
621 260 : const CharT* end = userbuf.rawCharPtrAt(position);
622 95936 : while (userbuf.addressOfNextRawChar() < end) {
623 : int32_t c;
624 47838 : if (!getChar(&c))
625 0 : return false;
626 : }
627 :
628 260 : Token* cur = &tokens[cursor];
629 260 : cur->pos.begin = userbuf.offset();
630 : MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type));
631 260 : lookahead = 0;
632 260 : return true;
633 : }
634 :
635 : void
636 81523 : TokenStream::tell(Position* pos)
637 : {
638 81523 : pos->buf = userbuf.addressOfNextRawChar(/* allowPoisoned = */ true);
639 81523 : pos->flags = flags;
640 81523 : pos->lineno = lineno;
641 81523 : pos->linebase = linebase;
642 81523 : pos->prevLinebase = prevLinebase;
643 81523 : pos->lookahead = lookahead;
644 81523 : pos->currentToken = currentToken();
645 202815 : for (unsigned i = 0; i < lookahead; i++)
646 121292 : pos->lookaheadTokens[i] = tokens[(cursor + 1 + i) & ntokensMask];
647 81523 : }
648 :
649 : void
650 4116 : TokenStream::seek(const Position& pos)
651 : {
652 4116 : userbuf.setAddressOfNextRawChar(pos.buf, /* allowPoisoned = */ true);
653 4116 : flags = pos.flags;
654 4116 : lineno = pos.lineno;
655 4116 : linebase = pos.linebase;
656 4116 : prevLinebase = pos.prevLinebase;
657 4116 : lookahead = pos.lookahead;
658 :
659 4116 : tokens[cursor] = pos.currentToken;
660 6587 : for (unsigned i = 0; i < lookahead; i++)
661 2471 : tokens[(cursor + 1 + i) & ntokensMask] = pos.lookaheadTokens[i];
662 4116 : }
663 :
664 : bool
665 3102 : TokenStream::seek(const Position& pos, const TokenStream& other)
666 : {
667 3102 : if (!srcCoords.fill(other.srcCoords))
668 0 : return false;
669 3102 : seek(pos);
670 3102 : return true;
671 : }
672 :
673 : bool
674 0 : TokenStream::reportStrictModeErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset,
675 : bool strictMode, unsigned errorNumber, va_list args)
676 : {
677 0 : if (!strictMode && !options().extraWarningsOption)
678 0 : return true;
679 :
680 0 : ErrorMetadata metadata;
681 0 : if (!computeErrorMetadata(&metadata, offset))
682 0 : return false;
683 :
684 0 : if (strictMode) {
685 0 : ReportCompileError(cx, Move(metadata), Move(notes), JSREPORT_ERROR, errorNumber, args);
686 0 : return false;
687 : }
688 :
689 0 : return compileWarning(Move(metadata), Move(notes), JSREPORT_WARNING | JSREPORT_STRICT,
690 0 : errorNumber, args);
691 : }
692 :
693 : bool
694 0 : TokenStreamAnyChars::compileWarning(ErrorMetadata&& metadata, UniquePtr<JSErrorNotes> notes,
695 : unsigned flags, unsigned errorNumber, va_list args)
696 : {
697 0 : if (options().werrorOption) {
698 0 : flags &= ~JSREPORT_WARNING;
699 0 : ReportCompileError(cx, Move(metadata), Move(notes), flags, errorNumber, args);
700 0 : return false;
701 : }
702 :
703 0 : return ReportCompileWarning(cx, Move(metadata), Move(notes), flags, errorNumber, args);
704 : }
705 :
706 : void
707 0 : TokenStreamAnyChars::computeErrorMetadataNoOffset(ErrorMetadata* err)
708 : {
709 0 : err->isMuted = mutedErrors;
710 0 : err->filename = filename;
711 0 : err->lineNumber = 0;
712 0 : err->columnNumber = 0;
713 :
714 0 : MOZ_ASSERT(err->lineOfContext == nullptr);
715 0 : }
716 :
717 : bool
718 0 : TokenStreamAnyChars::fillExcludingContext(ErrorMetadata* err, uint32_t offset)
719 : {
720 0 : err->isMuted = mutedErrors;
721 :
722 : // If this TokenStreamAnyChars doesn't have location information, try to
723 : // get it from the caller.
724 0 : if (!filename && !cx->helperThread()) {
725 0 : NonBuiltinFrameIter iter(cx,
726 : FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK,
727 0 : cx->compartment()->principals());
728 0 : if (!iter.done() && iter.filename()) {
729 0 : err->filename = iter.filename();
730 0 : err->lineNumber = iter.computeLine(&err->columnNumber);
731 0 : return false;
732 : }
733 : }
734 :
735 : // Otherwise use this TokenStreamAnyChars's location information.
736 0 : err->filename = filename;
737 0 : srcCoords.lineNumAndColumnIndex(offset, &err->lineNumber, &err->columnNumber);
738 0 : return true;
739 : }
740 :
741 : bool
742 0 : TokenStream::computeErrorMetadata(ErrorMetadata* err, uint32_t offset)
743 : {
744 0 : if (offset == NoOffset) {
745 0 : computeErrorMetadataNoOffset(err);
746 0 : return true;
747 : }
748 :
749 : // This function's return value isn't a success/failure indication: it
750 : // returns true if this TokenStream's location information could be used,
751 : // and it returns false when that information can't be used (and so we
752 : // can't provide a line of context).
753 0 : if (!fillExcludingContext(err, offset))
754 0 : return true;
755 :
756 : // Add a line of context from this TokenStream to help with debugging.
757 0 : return computeLineOfContext(err, offset);
758 : }
759 :
760 : bool
761 0 : TokenStream::computeLineOfContext(ErrorMetadata* err, uint32_t offset)
762 : {
763 : // This function presumes |err| is filled in *except* for line-of-context
764 : // fields. It exists to make |TokenStream::computeErrorMetadata|, above,
765 : // more readable.
766 :
767 : // We only have line-start information for the current line. If the error
768 : // is on a different line, we can't easily provide context. (This means
769 : // any error in a multi-line token, e.g. an unterminated multiline string
770 : // literal, won't have context.)
771 0 : if (err->lineNumber != lineno)
772 0 : return true;
773 :
774 0 : constexpr size_t windowRadius = ErrorMetadata::lineOfContextRadius;
775 :
776 : // The window must start within the current line, no earlier than
777 : // |windowRadius| characters before |offset|.
778 0 : MOZ_ASSERT(offset >= linebase);
779 0 : size_t windowStart = (offset - linebase > windowRadius) ?
780 0 : offset - windowRadius :
781 0 : linebase;
782 :
783 : // The window must start within the portion of the current line that we
784 : // actually have in our buffer.
785 0 : if (windowStart < userbuf.startOffset())
786 0 : windowStart = userbuf.startOffset();
787 :
788 : // The window must end within the current line, no later than
789 : // windowRadius after offset.
790 0 : size_t windowEnd = userbuf.findEOLMax(offset, windowRadius);
791 0 : size_t windowLength = windowEnd - windowStart;
792 0 : MOZ_ASSERT(windowLength <= windowRadius * 2);
793 :
794 : // Create the windowed string, not including the potential line
795 : // terminator.
796 0 : StringBuffer windowBuf(cx);
797 0 : if (!windowBuf.append(userbuf.rawCharPtrAt(windowStart), windowLength) ||
798 0 : !windowBuf.append('\0'))
799 : {
800 0 : return false;
801 : }
802 :
803 0 : err->lineOfContext.reset(windowBuf.stealChars());
804 0 : if (!err->lineOfContext)
805 0 : return false;
806 :
807 0 : err->lineLength = windowLength;
808 0 : err->tokenOffset = offset - windowStart;
809 0 : return true;
810 : }
811 :
812 : bool
813 0 : TokenStream::reportStrictModeError(unsigned errorNumber, ...)
814 : {
815 : va_list args;
816 0 : va_start(args, errorNumber);
817 0 : bool result = reportStrictModeErrorNumberVA(nullptr, currentToken().pos.begin, strictMode(),
818 0 : errorNumber, args);
819 0 : va_end(args);
820 0 : return result;
821 : }
822 :
823 : void
824 0 : TokenStream::reportError(unsigned errorNumber, ...)
825 : {
826 : va_list args;
827 0 : va_start(args, errorNumber);
828 :
829 0 : ErrorMetadata metadata;
830 0 : if (computeErrorMetadata(&metadata, currentToken().pos.begin))
831 0 : ReportCompileError(cx, Move(metadata), nullptr, JSREPORT_ERROR, errorNumber, args);
832 :
833 0 : va_end(args);
834 0 : }
835 :
836 : void
837 0 : TokenStreamAnyChars::reportErrorNoOffset(unsigned errorNumber, ...)
838 : {
839 : va_list args;
840 0 : va_start(args, errorNumber);
841 :
842 0 : ErrorMetadata metadata;
843 0 : computeErrorMetadataNoOffset(&metadata);
844 :
845 0 : ReportCompileError(cx, Move(metadata), nullptr, JSREPORT_ERROR, errorNumber, args);
846 :
847 0 : va_end(args);
848 0 : }
849 :
850 : bool
851 0 : TokenStream::warning(unsigned errorNumber, ...)
852 : {
853 : va_list args;
854 0 : va_start(args, errorNumber);
855 :
856 0 : ErrorMetadata metadata;
857 : bool result =
858 0 : computeErrorMetadata(&metadata, currentToken().pos.begin) &&
859 0 : compileWarning(Move(metadata), nullptr, JSREPORT_WARNING, errorNumber, args);
860 :
861 0 : va_end(args);
862 0 : return result;
863 : }
864 :
865 : bool
866 0 : TokenStream::reportExtraWarningErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset,
867 : unsigned errorNumber, va_list args)
868 : {
869 0 : if (!options().extraWarningsOption)
870 0 : return true;
871 :
872 0 : ErrorMetadata metadata;
873 0 : if (!computeErrorMetadata(&metadata, offset))
874 0 : return false;
875 :
876 0 : return compileWarning(Move(metadata), Move(notes), JSREPORT_STRICT | JSREPORT_WARNING,
877 0 : errorNumber, args);
878 : }
879 :
880 : void
881 0 : TokenStream::error(unsigned errorNumber, ...)
882 : {
883 : va_list args;
884 0 : va_start(args, errorNumber);
885 :
886 0 : ErrorMetadata metadata;
887 0 : if (computeErrorMetadata(&metadata, currentToken().pos.begin))
888 0 : ReportCompileError(cx, Move(metadata), nullptr, JSREPORT_ERROR, errorNumber, args);
889 :
890 0 : va_end(args);
891 0 : }
892 :
893 : void
894 0 : TokenStream::errorAt(uint32_t offset, unsigned errorNumber, ...)
895 : {
896 : va_list args;
897 0 : va_start(args, errorNumber);
898 :
899 0 : ErrorMetadata metadata;
900 0 : if (computeErrorMetadata(&metadata, offset))
901 0 : ReportCompileError(cx, Move(metadata), nullptr, JSREPORT_ERROR, errorNumber, args);
902 :
903 0 : va_end(args);
904 0 : }
905 :
906 : // We have encountered a '\': check for a Unicode escape sequence after it.
907 : // Return the length of the escape sequence and the character code point (by
908 : // value) if we found a Unicode escape sequence. Otherwise, return 0. In both
909 : // cases, do not advance along the buffer.
910 : uint32_t
911 0 : TokenStream::peekUnicodeEscape(uint32_t* codePoint)
912 : {
913 0 : int32_t c = getCharIgnoreEOL();
914 0 : if (c != 'u') {
915 0 : ungetCharIgnoreEOL(c);
916 0 : return 0;
917 : }
918 :
919 : CharT cp[3];
920 : uint32_t length;
921 0 : c = getCharIgnoreEOL();
922 0 : if (JS7_ISHEX(c) && peekChars(3, cp) &&
923 0 : JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]))
924 : {
925 0 : *codePoint = (JS7_UNHEX(c) << 12) |
926 0 : (JS7_UNHEX(cp[0]) << 8) |
927 0 : (JS7_UNHEX(cp[1]) << 4) |
928 0 : JS7_UNHEX(cp[2]);
929 0 : length = 5;
930 0 : } else if (c == '{') {
931 0 : length = peekExtendedUnicodeEscape(codePoint);
932 : } else {
933 0 : length = 0;
934 : }
935 :
936 0 : ungetCharIgnoreEOL(c);
937 0 : ungetCharIgnoreEOL('u');
938 0 : return length;
939 : }
940 :
941 : uint32_t
942 0 : TokenStream::peekExtendedUnicodeEscape(uint32_t* codePoint)
943 : {
944 : // The opening brace character was already read.
945 0 : int32_t c = getCharIgnoreEOL();
946 :
947 : // Skip leading zeros.
948 0 : uint32_t leadingZeros = 0;
949 0 : while (c == '0') {
950 0 : leadingZeros++;
951 0 : c = getCharIgnoreEOL();
952 : }
953 :
954 : CharT cp[6];
955 0 : size_t i = 0;
956 0 : uint32_t code = 0;
957 0 : while (JS7_ISHEX(c) && i < 6) {
958 0 : cp[i++] = c;
959 0 : code = code << 4 | JS7_UNHEX(c);
960 0 : c = getCharIgnoreEOL();
961 : }
962 :
963 : uint32_t length;
964 0 : if (c == '}' && (leadingZeros > 0 || i > 0) && code <= unicode::NonBMPMax) {
965 0 : *codePoint = code;
966 0 : length = leadingZeros + i + 3;
967 : } else {
968 0 : length = 0;
969 : }
970 :
971 0 : ungetCharIgnoreEOL(c);
972 0 : while (i--)
973 0 : ungetCharIgnoreEOL(cp[i]);
974 0 : while (leadingZeros--)
975 0 : ungetCharIgnoreEOL('0');
976 :
977 0 : return length;
978 : }
979 :
980 : uint32_t
981 0 : TokenStream::matchUnicodeEscapeIdStart(uint32_t* codePoint)
982 : {
983 0 : uint32_t length = peekUnicodeEscape(codePoint);
984 0 : if (length > 0 && unicode::IsIdentifierStart(*codePoint)) {
985 0 : skipChars(length);
986 0 : return length;
987 : }
988 0 : return 0;
989 : }
990 :
991 : bool
992 0 : TokenStream::matchUnicodeEscapeIdent(uint32_t* codePoint)
993 : {
994 0 : uint32_t length = peekUnicodeEscape(codePoint);
995 0 : if (length > 0 && unicode::IsIdentifierPart(*codePoint)) {
996 0 : skipChars(length);
997 0 : return true;
998 : }
999 0 : return false;
1000 : }
1001 :
1002 : // Helper function which returns true if the first length(q) characters in p are
1003 : // the same as the characters in q.
1004 : template<typename CharT>
1005 : static bool
1006 4598 : CharsMatch(const CharT* p, const char* q)
1007 : {
1008 4736 : while (*q) {
1009 4596 : if (*p++ != *q++)
1010 4458 : return false;
1011 : }
1012 :
1013 2 : return true;
1014 : }
1015 :
1016 : bool
1017 2588 : TokenStream::getDirectives(bool isMultiline, bool shouldWarnDeprecated)
1018 : {
1019 : // Match directive comments used in debugging, such as "//# sourceURL" and
1020 : // "//# sourceMappingURL". Use of "//@" instead of "//#" is deprecated.
1021 : //
1022 : // To avoid a crashing bug in IE, several JavaScript transpilers wrap single
1023 : // line comments containing a source mapping URL inside a multiline
1024 : // comment. To avoid potentially expensive lookahead and backtracking, we
1025 : // only check for this case if we encounter a '#' character.
1026 :
1027 2588 : if (!getDisplayURL(isMultiline, shouldWarnDeprecated))
1028 0 : return false;
1029 2588 : if (!getSourceMappingURL(isMultiline, shouldWarnDeprecated))
1030 0 : return false;
1031 :
1032 2588 : return true;
1033 : }
1034 :
1035 : bool
1036 5176 : TokenStream::getDirective(bool isMultiline, bool shouldWarnDeprecated,
1037 : const char* directive, uint8_t directiveLength,
1038 : const char* errorMsgPragma,
1039 : UniqueTwoByteChars* destination)
1040 : {
1041 5176 : MOZ_ASSERT(directiveLength <= 18);
1042 : char16_t peeked[18];
1043 :
1044 5176 : if (peekChars(directiveLength, peeked) && CharsMatch(peeked, directive)) {
1045 2 : if (shouldWarnDeprecated) {
1046 0 : if (!warning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma))
1047 0 : return false;
1048 : }
1049 :
1050 2 : skipChars(directiveLength);
1051 2 : tokenbuf.clear();
1052 :
1053 : do {
1054 : int32_t c;
1055 107 : if (!peekChar(&c))
1056 0 : return false;
1057 :
1058 107 : if (c == EOF || unicode::IsSpaceOrBOM2(c))
1059 4 : break;
1060 :
1061 105 : consumeKnownChar(c);
1062 :
1063 : // Debugging directives can occur in both single- and multi-line
1064 : // comments. If we're currently inside a multi-line comment, we also
1065 : // need to recognize multi-line comment terminators.
1066 105 : if (isMultiline && c == '*') {
1067 : int32_t c2;
1068 0 : if (!peekChar(&c2))
1069 0 : return false;
1070 :
1071 0 : if (c2 == '/') {
1072 0 : ungetChar('*');
1073 0 : break;
1074 : }
1075 : }
1076 :
1077 105 : if (!tokenbuf.append(c))
1078 0 : return false;
1079 : } while (true);
1080 :
1081 2 : if (tokenbuf.empty()) {
1082 : // The directive's URL was missing, but this is not quite an
1083 : // exception that we should stop and drop everything for.
1084 0 : return true;
1085 : }
1086 :
1087 2 : size_t length = tokenbuf.length();
1088 :
1089 2 : *destination = cx->make_pod_array<char16_t>(length + 1);
1090 2 : if (!*destination)
1091 0 : return false;
1092 :
1093 2 : PodCopy(destination->get(), tokenbuf.begin(), length);
1094 2 : (*destination)[length] = '\0';
1095 : }
1096 :
1097 5176 : return true;
1098 : }
1099 :
1100 : bool
1101 2588 : TokenStream::getDisplayURL(bool isMultiline, bool shouldWarnDeprecated)
1102 : {
1103 : // Match comments of the form "//# sourceURL=<url>" or
1104 : // "/\* //# sourceURL=<url> *\/"
1105 : //
1106 : // Note that while these are labeled "sourceURL" in the source text,
1107 : // internally we refer to it as a "displayURL" to distinguish what the
1108 : // developer would like to refer to the source as from the source's actual
1109 : // URL.
1110 :
1111 : static const char sourceURLDirective[] = " sourceURL=";
1112 2588 : constexpr uint8_t sourceURLDirectiveLength = ArrayLength(sourceURLDirective) - 1;
1113 2588 : return getDirective(isMultiline, shouldWarnDeprecated,
1114 : sourceURLDirective, sourceURLDirectiveLength,
1115 2588 : "sourceURL", &displayURL_);
1116 : }
1117 :
1118 : bool
1119 2588 : TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated)
1120 : {
1121 : // Match comments of the form "//# sourceMappingURL=<url>" or
1122 : // "/\* //# sourceMappingURL=<url> *\/"
1123 :
1124 : static const char sourceMappingURLDirective[] = " sourceMappingURL=";
1125 2588 : constexpr uint8_t sourceMappingURLDirectiveLength = ArrayLength(sourceMappingURLDirective) - 1;
1126 2588 : return getDirective(isMultiline, shouldWarnDeprecated,
1127 : sourceMappingURLDirective, sourceMappingURLDirectiveLength,
1128 2588 : "sourceMappingURL", &sourceMapURL_);
1129 : }
1130 :
1131 : MOZ_ALWAYS_INLINE Token*
1132 565076 : TokenStream::newToken(ptrdiff_t adjust)
1133 : {
1134 565076 : cursor = (cursor + 1) & ntokensMask;
1135 565076 : Token* tp = &tokens[cursor];
1136 565076 : tp->pos.begin = userbuf.offset() + adjust;
1137 :
1138 : // NOTE: tp->pos.end is not set until the very end of getTokenInternal().
1139 : MOZ_MAKE_MEM_UNDEFINED(&tp->pos.end, sizeof(tp->pos.end));
1140 :
1141 565076 : return tp;
1142 : }
1143 :
1144 : MOZ_ALWAYS_INLINE JSAtom*
1145 21832 : TokenStream::atomize(JSContext* cx, CharBuffer& cb)
1146 : {
1147 21832 : return AtomizeChars(cx, cb.begin(), cb.length());
1148 : }
1149 :
1150 : #ifdef DEBUG
1151 : static bool
1152 555417 : IsTokenSane(Token* tp)
1153 : {
1154 : // Nb: TOK_EOL should never be used in an actual Token; it should only be
1155 : // returned as a TokenKind from peekTokenSameLine().
1156 555417 : if (tp->type < 0 || tp->type >= TOK_LIMIT || tp->type == TOK_EOL)
1157 0 : return false;
1158 :
1159 555417 : if (tp->pos.end < tp->pos.begin)
1160 0 : return false;
1161 :
1162 555417 : return true;
1163 : }
1164 : #endif
1165 :
1166 : bool
1167 0 : TokenStream::matchTrailForLeadSurrogate(char16_t lead, char16_t* trail, uint32_t* codePoint)
1168 : {
1169 0 : int32_t maybeTrail = getCharIgnoreEOL();
1170 0 : if (!unicode::IsTrailSurrogate(maybeTrail)) {
1171 0 : ungetCharIgnoreEOL(maybeTrail);
1172 0 : return false;
1173 : }
1174 :
1175 0 : if (trail)
1176 0 : *trail = maybeTrail;
1177 0 : *codePoint = unicode::UTF16Decode(lead, maybeTrail);
1178 0 : return true;
1179 : }
1180 :
1181 : bool
1182 0 : TokenStream::putIdentInTokenbuf(const char16_t* identStart)
1183 : {
1184 : int32_t c;
1185 : uint32_t qc;
1186 0 : const CharT* tmp = userbuf.addressOfNextRawChar();
1187 0 : userbuf.setAddressOfNextRawChar(identStart);
1188 :
1189 0 : tokenbuf.clear();
1190 : for (;;) {
1191 0 : c = getCharIgnoreEOL();
1192 :
1193 0 : if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) {
1194 : char16_t trail;
1195 : uint32_t codePoint;
1196 0 : if (matchTrailForLeadSurrogate(c, &trail, &codePoint)) {
1197 0 : if (!unicode::IsIdentifierPart(codePoint))
1198 0 : break;
1199 :
1200 0 : if (!tokenbuf.append(c) || !tokenbuf.append(trail)) {
1201 0 : userbuf.setAddressOfNextRawChar(tmp);
1202 0 : return false;
1203 : }
1204 0 : continue;
1205 : }
1206 : }
1207 :
1208 0 : if (!unicode::IsIdentifierPart(char16_t(c))) {
1209 0 : if (c != '\\' || !matchUnicodeEscapeIdent(&qc))
1210 0 : break;
1211 :
1212 0 : if (MOZ_UNLIKELY(unicode::IsSupplementary(qc))) {
1213 : char16_t lead, trail;
1214 0 : unicode::UTF16Encode(qc, &lead, &trail);
1215 0 : if (!tokenbuf.append(lead) || !tokenbuf.append(trail)) {
1216 0 : userbuf.setAddressOfNextRawChar(tmp);
1217 0 : return false;
1218 : }
1219 0 : continue;
1220 : }
1221 :
1222 0 : c = qc;
1223 : }
1224 :
1225 0 : if (!tokenbuf.append(c)) {
1226 0 : userbuf.setAddressOfNextRawChar(tmp);
1227 0 : return false;
1228 : }
1229 0 : }
1230 0 : userbuf.setAddressOfNextRawChar(tmp);
1231 0 : return true;
1232 : }
1233 :
1234 : enum FirstCharKind {
1235 : // A char16_t has the 'OneChar' kind if it, by itself, constitutes a valid
1236 : // token that cannot also be a prefix of a longer token. E.g. ';' has the
1237 : // OneChar kind, but '+' does not, because '++' and '+=' are valid longer tokens
1238 : // that begin with '+'.
1239 : //
1240 : // The few token kinds satisfying these properties cover roughly 35--45%
1241 : // of the tokens seen in practice.
1242 : //
1243 : // We represent the 'OneChar' kind with any positive value less than
1244 : // TOK_LIMIT. This representation lets us associate each one-char token
1245 : // char16_t with a TokenKind and thus avoid a subsequent char16_t-to-TokenKind
1246 : // conversion.
1247 : OneChar_Min = 0,
1248 : OneChar_Max = TOK_LIMIT - 1,
1249 :
1250 : Space = TOK_LIMIT,
1251 : Ident,
1252 : Dec,
1253 : String,
1254 : EOL,
1255 : BasePrefix,
1256 : Other,
1257 :
1258 : LastCharKind = Other
1259 : };
1260 :
1261 : // OneChar: 40, 41, 44, 58, 59, 63, 91, 93, 123, 125, 126:
1262 : // '(', ')', ',', ':', ';', '?', '[', ']', '{', '}', '~'
1263 : // Ident: 36, 65..90, 95, 97..122: '$', 'A'..'Z', '_', 'a'..'z'
1264 : // Dot: 46: '.'
1265 : // Equals: 61: '='
1266 : // String: 34, 39: '"', '\''
1267 : // Dec: 49..57: '1'..'9'
1268 : // Plus: 43: '+'
1269 : // BasePrefix: 48: '0'
1270 : // Space: 9, 11, 12, 32: '\t', '\v', '\f', ' '
1271 : // EOL: 10, 13: '\n', '\r'
1272 : //
1273 : #define T_COMMA TOK_COMMA
1274 : #define T_COLON TOK_COLON
1275 : #define T_BITNOT TOK_BITNOT
1276 : #define Templat String
1277 : #define _______ Other
1278 : static const uint8_t firstCharKinds[] = {
1279 : /* 0 1 2 3 4 5 6 7 8 9 */
1280 : /* 0+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, Space,
1281 : /* 10+ */ EOL, Space, Space, EOL, _______, _______, _______, _______, _______, _______,
1282 : /* 20+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
1283 : /* 30+ */ _______, _______, Space, _______, String, _______, Ident, _______, _______, String,
1284 : /* 40+ */ TOK_LP, TOK_RP, _______, _______, T_COMMA,_______, _______, _______,BasePrefix, Dec,
1285 : /* 50+ */ Dec, Dec, Dec, Dec, Dec, Dec, Dec, Dec, T_COLON,TOK_SEMI,
1286 : /* 60+ */ _______, _______, _______,TOK_HOOK, _______, Ident, Ident, Ident, Ident, Ident,
1287 : /* 70+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
1288 : /* 80+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
1289 : /* 90+ */ Ident, TOK_LB, _______, TOK_RB, _______, Ident, Templat, Ident, Ident, Ident,
1290 : /* 100+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
1291 : /* 110+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
1292 : /* 120+ */ Ident, Ident, Ident, TOK_LC, _______, TOK_RC,T_BITNOT, _______
1293 : };
1294 : #undef T_COMMA
1295 : #undef T_COLON
1296 : #undef T_BITNOT
1297 : #undef Templat
1298 : #undef _______
1299 :
1300 : static_assert(LastCharKind < (1 << (sizeof(firstCharKinds[0]) * 8)),
1301 : "Elements of firstCharKinds[] are too small");
1302 :
1303 : bool
1304 555417 : TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
1305 : {
1306 : int c;
1307 : uint32_t qc;
1308 : Token* tp;
1309 : FirstCharKind c1kind;
1310 : const CharT* numStart;
1311 : bool hasExp;
1312 : DecimalPoint decimalPoint;
1313 : const CharT* identStart;
1314 : bool hadUnicodeEscape;
1315 :
1316 : // Check if in the middle of a template string. Have to get this out of
1317 : // the way first.
1318 555417 : if (MOZ_UNLIKELY(modifier == TemplateTail)) {
1319 665 : if (!getStringOrTemplateToken('`', &tp))
1320 0 : goto error;
1321 665 : goto out;
1322 : }
1323 :
1324 : retry:
1325 1377600 : if (MOZ_UNLIKELY(!userbuf.hasRawChars())) {
1326 616 : tp = newToken(0);
1327 616 : tp->type = TOK_EOF;
1328 616 : flags.isEOF = true;
1329 616 : goto out;
1330 : }
1331 :
1332 1376984 : c = userbuf.getRawChar();
1333 1376984 : MOZ_ASSERT(c != EOF);
1334 :
1335 : // Chars not in the range 0..127 are rare. Getting them out of the way
1336 : // early allows subsequent checking to be faster.
1337 1376984 : if (MOZ_UNLIKELY(c >= 128)) {
1338 0 : if (unicode::IsSpaceOrBOM2(c)) {
1339 0 : if (c == unicode::LINE_SEPARATOR || c == unicode::PARA_SEPARATOR) {
1340 0 : if (!updateLineInfoForEOL())
1341 0 : goto error;
1342 :
1343 0 : updateFlagsForEOL();
1344 : }
1345 :
1346 0 : goto retry;
1347 : }
1348 :
1349 0 : tp = newToken(-1);
1350 :
1351 : static_assert('$' < 128,
1352 : "IdentifierStart contains '$', but as !IsUnicodeIDStart('$'), "
1353 : "ensure that '$' is never handled here");
1354 : static_assert('_' < 128,
1355 : "IdentifierStart contains '_', but as !IsUnicodeIDStart('_'), "
1356 : "ensure that '_' is never handled here");
1357 0 : if (unicode::IsUnicodeIDStart(char16_t(c))) {
1358 0 : identStart = userbuf.addressOfNextRawChar() - 1;
1359 0 : hadUnicodeEscape = false;
1360 0 : goto identifier;
1361 : }
1362 :
1363 0 : if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) {
1364 : uint32_t codePoint;
1365 0 : if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) &&
1366 0 : unicode::IsUnicodeIDStart(codePoint))
1367 : {
1368 0 : identStart = userbuf.addressOfNextRawChar() - 2;
1369 0 : hadUnicodeEscape = false;
1370 0 : goto identifier;
1371 : }
1372 : }
1373 :
1374 0 : goto badchar;
1375 : }
1376 :
1377 : // Get the token kind, based on the first char. The ordering of c1kind
1378 : // comparison is based on the frequency of tokens in real code -- Parsemark
1379 : // (which represents typical JS code on the web) and the Unreal demo (which
1380 : // represents asm.js code).
1381 : //
1382 : // Parsemark Unreal
1383 : // OneChar 32.9% 39.7%
1384 : // Space 25.0% 0.6%
1385 : // Ident 19.2% 36.4%
1386 : // Dec 7.2% 5.1%
1387 : // String 7.9% 0.0%
1388 : // EOL 1.7% 0.0%
1389 : // BasePrefix 0.4% 4.9%
1390 : // Other 5.7% 13.3%
1391 : //
1392 : // The ordering is based mostly only Parsemark frequencies, with Unreal
1393 : // frequencies used to break close categories (e.g. |Dec| and |String|).
1394 : // |Other| is biggish, but no other token kind is common enough for it to
1395 : // be worth adding extra values to FirstCharKind.
1396 : //
1397 1376984 : c1kind = FirstCharKind(firstCharKinds[c]);
1398 :
1399 : // Look for an unambiguous single-char token.
1400 : //
1401 1376984 : if (c1kind <= OneChar_Max) {
1402 228472 : tp = newToken(-1);
1403 228472 : tp->type = TokenKind(c1kind);
1404 228472 : goto out;
1405 : }
1406 :
1407 : // Skip over non-EOL whitespace chars.
1408 : //
1409 1148512 : if (c1kind == Space)
1410 710488 : goto retry;
1411 :
1412 : // Look for an identifier.
1413 : //
1414 438024 : if (c1kind == Ident) {
1415 213027 : tp = newToken(-1);
1416 213027 : identStart = userbuf.addressOfNextRawChar() - 1;
1417 213027 : hadUnicodeEscape = false;
1418 :
1419 : identifier:
1420 : for (;;) {
1421 1626860 : c = getCharIgnoreEOL();
1422 1626860 : if (c == EOF)
1423 100 : break;
1424 :
1425 1626760 : if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) {
1426 : uint32_t codePoint;
1427 0 : if (matchTrailForLeadSurrogate(c, nullptr, &codePoint)) {
1428 0 : if (!unicode::IsIdentifierPart(codePoint))
1429 0 : break;
1430 :
1431 0 : continue;
1432 : }
1433 : }
1434 :
1435 1626760 : if (!unicode::IsIdentifierPart(char16_t(c))) {
1436 212927 : if (c != '\\' || !matchUnicodeEscapeIdent(&qc))
1437 212927 : break;
1438 0 : hadUnicodeEscape = true;
1439 : }
1440 1413833 : }
1441 213027 : ungetCharIgnoreEOL(c);
1442 :
1443 : // Identifiers containing no Unicode escapes can be processed directly
1444 : // from userbuf. The rest must use the escapes converted via tokenbuf
1445 : // before atomizing.
1446 : const CharT* chars;
1447 : size_t length;
1448 213027 : if (hadUnicodeEscape) {
1449 0 : if (!putIdentInTokenbuf(identStart))
1450 0 : goto error;
1451 :
1452 0 : chars = tokenbuf.begin();
1453 0 : length = tokenbuf.length();
1454 : } else {
1455 213027 : chars = identStart;
1456 213027 : length = userbuf.addressOfNextRawChar() - identStart;
1457 : }
1458 :
1459 : // Represent reserved words as reserved word tokens.
1460 213027 : if (!hadUnicodeEscape) {
1461 213027 : if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) {
1462 60844 : tp->type = rw->tokentype;
1463 60844 : goto out;
1464 : }
1465 : }
1466 :
1467 152183 : JSAtom* atom = AtomizeChars(cx, chars, length);
1468 152183 : if (!atom)
1469 0 : goto error;
1470 152183 : tp->type = TOK_NAME;
1471 152183 : tp->setName(atom->asPropertyName());
1472 152183 : goto out;
1473 : }
1474 :
1475 : // Look for a decimal number.
1476 : //
1477 224997 : if (c1kind == Dec) {
1478 6378 : tp = newToken(-1);
1479 6378 : numStart = userbuf.addressOfNextRawChar() - 1;
1480 :
1481 : decimal:
1482 9627 : decimalPoint = NoDecimal;
1483 9627 : hasExp = false;
1484 29953 : while (JS7_ISDEC(c))
1485 10163 : c = getCharIgnoreEOL();
1486 :
1487 9627 : if (c == '.') {
1488 23 : decimalPoint = HasDecimal;
1489 : decimal_dot:
1490 26 : do {
1491 51 : c = getCharIgnoreEOL();
1492 51 : } while (JS7_ISDEC(c));
1493 : }
1494 9629 : if (c == 'e' || c == 'E') {
1495 0 : hasExp = true;
1496 0 : c = getCharIgnoreEOL();
1497 0 : if (c == '+' || c == '-')
1498 0 : c = getCharIgnoreEOL();
1499 0 : if (!JS7_ISDEC(c)) {
1500 0 : ungetCharIgnoreEOL(c);
1501 0 : reportError(JSMSG_MISSING_EXPONENT);
1502 0 : goto error;
1503 : }
1504 0 : do {
1505 0 : c = getCharIgnoreEOL();
1506 0 : } while (JS7_ISDEC(c));
1507 : }
1508 9629 : ungetCharIgnoreEOL(c);
1509 :
1510 9629 : if (c != EOF) {
1511 9622 : if (unicode::IsIdentifierStart(char16_t(c))) {
1512 0 : reportError(JSMSG_IDSTART_AFTER_NUMBER);
1513 0 : goto error;
1514 : }
1515 :
1516 9622 : if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) {
1517 : uint32_t codePoint;
1518 0 : if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) &&
1519 0 : unicode::IsIdentifierStart(codePoint))
1520 : {
1521 0 : reportError(JSMSG_IDSTART_AFTER_NUMBER);
1522 0 : goto error;
1523 : }
1524 : }
1525 : }
1526 :
1527 : // Unlike identifiers and strings, numbers cannot contain escaped
1528 : // chars, so we don't need to use tokenbuf. Instead we can just
1529 : // convert the char16_t characters in userbuf to the numeric value.
1530 : double dval;
1531 9629 : if (!((decimalPoint == HasDecimal) || hasExp)) {
1532 19208 : if (!GetDecimalInteger(cx, numStart, userbuf.addressOfNextRawChar(), &dval))
1533 0 : goto error;
1534 : } else {
1535 : const CharT* dummy;
1536 25 : if (!js_strtod(cx, numStart, userbuf.addressOfNextRawChar(), &dummy, &dval))
1537 0 : goto error;
1538 : }
1539 9629 : tp->type = TOK_NUMBER;
1540 9629 : tp->setNumber(dval, decimalPoint);
1541 9629 : goto out;
1542 : }
1543 :
1544 : // Look for a string or a template string.
1545 : //
1546 218619 : if (c1kind == String) {
1547 21167 : if (!getStringOrTemplateToken(c, &tp))
1548 0 : goto error;
1549 21167 : goto out;
1550 : }
1551 :
1552 : // Skip over EOL chars, updating line state along the way.
1553 : //
1554 197452 : if (c1kind == EOL) {
1555 : // If it's a \r\n sequence: treat as a single EOL, skip over the \n.
1556 102701 : if (c == '\r' && userbuf.hasRawChars())
1557 0 : userbuf.matchRawChar('\n');
1558 102701 : if (!updateLineInfoForEOL())
1559 0 : goto error;
1560 102701 : updateFlagsForEOL();
1561 102701 : goto retry;
1562 : }
1563 :
1564 : // Look for a hexadecimal, octal, or binary number.
1565 : //
1566 94751 : if (c1kind == BasePrefix) {
1567 3591 : tp = newToken(-1);
1568 : int radix;
1569 3591 : c = getCharIgnoreEOL();
1570 3930 : if (c == 'x' || c == 'X') {
1571 339 : radix = 16;
1572 339 : c = getCharIgnoreEOL();
1573 339 : if (!JS7_ISHEX(c)) {
1574 0 : ungetCharIgnoreEOL(c);
1575 0 : reportError(JSMSG_MISSING_HEXDIGITS);
1576 0 : goto error;
1577 : }
1578 339 : numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0x'
1579 2789 : while (JS7_ISHEX(c))
1580 1225 : c = getCharIgnoreEOL();
1581 3252 : } else if (c == 'b' || c == 'B') {
1582 0 : radix = 2;
1583 0 : c = getCharIgnoreEOL();
1584 0 : if (c != '0' && c != '1') {
1585 0 : ungetCharIgnoreEOL(c);
1586 0 : reportError(JSMSG_MISSING_BINARY_DIGITS);
1587 0 : goto error;
1588 : }
1589 0 : numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0b'
1590 0 : while (c == '0' || c == '1')
1591 0 : c = getCharIgnoreEOL();
1592 3255 : } else if (c == 'o' || c == 'O') {
1593 3 : radix = 8;
1594 3 : c = getCharIgnoreEOL();
1595 3 : if (c < '0' || c > '7') {
1596 0 : ungetCharIgnoreEOL(c);
1597 0 : reportError(JSMSG_MISSING_OCTAL_DIGITS);
1598 0 : goto error;
1599 : }
1600 3 : numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0o'
1601 21 : while ('0' <= c && c <= '7')
1602 9 : c = getCharIgnoreEOL();
1603 3249 : } else if (JS7_ISDEC(c)) {
1604 0 : radix = 8;
1605 0 : numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0'
1606 0 : while (JS7_ISDEC(c)) {
1607 : // Octal integer literals are not permitted in strict mode code.
1608 0 : if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
1609 0 : goto error;
1610 :
1611 : // Outside strict mode, we permit 08 and 09 as decimal numbers,
1612 : // which makes our behaviour a superset of the ECMA numeric
1613 : // grammar. We might not always be so permissive, so we warn
1614 : // about it.
1615 0 : if (c >= '8') {
1616 0 : if (!warning(JSMSG_BAD_OCTAL, c == '8' ? "08" : "09"))
1617 0 : goto error;
1618 :
1619 : // Use the decimal scanner for the rest of the number.
1620 3249 : goto decimal;
1621 : }
1622 0 : c = getCharIgnoreEOL();
1623 : }
1624 : } else {
1625 : // '0' not followed by 'x', 'X' or a digit; scan as a decimal number.
1626 3249 : numStart = userbuf.addressOfNextRawChar() - 1;
1627 3249 : goto decimal;
1628 : }
1629 342 : ungetCharIgnoreEOL(c);
1630 :
1631 342 : if (c != EOF) {
1632 342 : if (unicode::IsIdentifierStart(char16_t(c))) {
1633 0 : reportError(JSMSG_IDSTART_AFTER_NUMBER);
1634 0 : goto error;
1635 : }
1636 :
1637 342 : if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) {
1638 : uint32_t codePoint;
1639 0 : if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) &&
1640 0 : unicode::IsIdentifierStart(codePoint))
1641 : {
1642 0 : reportError(JSMSG_IDSTART_AFTER_NUMBER);
1643 0 : goto error;
1644 : }
1645 : }
1646 : }
1647 :
1648 : double dval;
1649 : const char16_t* dummy;
1650 342 : if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix, &dummy, &dval))
1651 0 : goto error;
1652 342 : tp->type = TOK_NUMBER;
1653 342 : tp->setNumber(dval, NoDecimal);
1654 342 : goto out;
1655 : }
1656 :
1657 : // This handles everything else.
1658 : //
1659 91160 : MOZ_ASSERT(c1kind == Other);
1660 91160 : tp = newToken(-1);
1661 91160 : switch (c) {
1662 : case '.':
1663 40457 : c = getCharIgnoreEOL();
1664 40457 : if (JS7_ISDEC(c)) {
1665 2 : numStart = userbuf.addressOfNextRawChar() - 2;
1666 2 : decimalPoint = HasDecimal;
1667 2 : hasExp = false;
1668 2 : goto decimal_dot;
1669 : }
1670 40455 : if (c == '.') {
1671 152 : if (matchChar('.')) {
1672 152 : tp->type = TOK_TRIPLEDOT;
1673 152 : goto out;
1674 : }
1675 : }
1676 40303 : ungetCharIgnoreEOL(c);
1677 40303 : tp->type = TOK_DOT;
1678 40303 : goto out;
1679 :
1680 : case '=':
1681 22770 : if (matchChar('='))
1682 2858 : tp->type = matchChar('=') ? TOK_STRICTEQ : TOK_EQ;
1683 19912 : else if (matchChar('>'))
1684 1851 : tp->type = TOK_ARROW;
1685 : else
1686 18061 : tp->type = TOK_ASSIGN;
1687 22770 : goto out;
1688 :
1689 : case '+':
1690 6652 : if (matchChar('+'))
1691 712 : tp->type = TOK_INC;
1692 : else
1693 5940 : tp->type = matchChar('=') ? TOK_ADDASSIGN : TOK_ADD;
1694 6652 : goto out;
1695 :
1696 : case '\\': {
1697 0 : uint32_t escapeLength = matchUnicodeEscapeIdStart(&qc);
1698 0 : if (escapeLength > 0) {
1699 0 : identStart = userbuf.addressOfNextRawChar() - escapeLength - 1;
1700 0 : hadUnicodeEscape = true;
1701 0 : goto identifier;
1702 : }
1703 0 : goto badchar;
1704 : }
1705 :
1706 : case '|':
1707 1689 : if (matchChar('|'))
1708 1459 : tp->type = TOK_OR;
1709 : else
1710 230 : tp->type = matchChar('=') ? TOK_BITORASSIGN : TOK_BITOR;
1711 1689 : goto out;
1712 :
1713 : case '^':
1714 18 : tp->type = matchChar('=') ? TOK_BITXORASSIGN : TOK_BITXOR;
1715 18 : goto out;
1716 :
1717 : case '&':
1718 1807 : if (matchChar('&'))
1719 1642 : tp->type = TOK_AND;
1720 : else
1721 165 : tp->type = matchChar('=') ? TOK_BITANDASSIGN : TOK_BITAND;
1722 1807 : goto out;
1723 :
1724 : case '!':
1725 4786 : if (matchChar('='))
1726 1183 : tp->type = matchChar('=') ? TOK_STRICTNE : TOK_NE;
1727 : else
1728 3603 : tp->type = TOK_NOT;
1729 4786 : goto out;
1730 :
1731 : case '<':
1732 1154 : if (options().allowHTMLComments) {
1733 : // Treat HTML begin-comment as comment-till-end-of-line.
1734 1154 : if (matchChar('!')) {
1735 0 : if (matchChar('-')) {
1736 0 : if (matchChar('-'))
1737 0 : goto skipline;
1738 0 : ungetChar('-');
1739 : }
1740 0 : ungetChar('!');
1741 : }
1742 : }
1743 1154 : if (matchChar('<')) {
1744 23 : tp->type = matchChar('=') ? TOK_LSHASSIGN : TOK_LSH;
1745 : } else {
1746 1131 : tp->type = matchChar('=') ? TOK_LE : TOK_LT;
1747 : }
1748 1154 : goto out;
1749 :
1750 : case '>':
1751 781 : if (matchChar('>')) {
1752 56 : if (matchChar('>'))
1753 21 : tp->type = matchChar('=') ? TOK_URSHASSIGN : TOK_URSH;
1754 : else
1755 35 : tp->type = matchChar('=') ? TOK_RSHASSIGN : TOK_RSH;
1756 : } else {
1757 725 : tp->type = matchChar('=') ? TOK_GE : TOK_GT;
1758 : }
1759 781 : goto out;
1760 :
1761 : case '*':
1762 224 : if (matchChar('*'))
1763 0 : tp->type = matchChar('=') ? TOK_POWASSIGN : TOK_POW;
1764 : else
1765 224 : tp->type = matchChar('=') ? TOK_MULASSIGN : TOK_MUL;
1766 224 : goto out;
1767 :
1768 : case '/':
1769 : // Look for a single-line comment.
1770 9963 : if (matchChar('/')) {
1771 7439 : if (!peekChar(&c))
1772 0 : goto error;
1773 7439 : if (c == '@' || c == '#') {
1774 279 : consumeKnownChar(c);
1775 :
1776 279 : bool shouldWarn = c == '@';
1777 279 : if (!getDirectives(false, shouldWarn))
1778 0 : goto error;
1779 : }
1780 :
1781 : skipline:
1782 397755 : do {
1783 405194 : if (!getChar(&c))
1784 0 : goto error;
1785 405194 : } while (c != EOF && c != '\n');
1786 :
1787 7439 : ungetChar(c);
1788 7439 : cursor = (cursor - 1) & ntokensMask;
1789 7439 : goto retry;
1790 : }
1791 :
1792 : // Look for a multi-line comment.
1793 2524 : if (matchChar('*')) {
1794 2220 : unsigned linenoBefore = lineno;
1795 :
1796 : do {
1797 547068 : if (!getChar(&c))
1798 0 : return false;
1799 :
1800 547068 : if (c == EOF) {
1801 0 : reportError(JSMSG_UNTERMINATED_COMMENT);
1802 0 : goto error;
1803 : }
1804 :
1805 547068 : if (c == '*' && matchChar('/'))
1806 2220 : break;
1807 :
1808 544848 : if (c == '@' || c == '#') {
1809 2309 : bool shouldWarn = c == '@';
1810 2309 : if (!getDirectives(true, shouldWarn))
1811 0 : goto error;
1812 544848 : }
1813 : } while (true);
1814 :
1815 2220 : if (linenoBefore != lineno)
1816 1919 : updateFlagsForEOL();
1817 2220 : cursor = (cursor - 1) & ntokensMask;
1818 2220 : goto retry;
1819 : }
1820 :
1821 : // Look for a regexp.
1822 304 : if (modifier == Operand) {
1823 210 : tokenbuf.clear();
1824 :
1825 210 : bool inCharClass = false;
1826 : do {
1827 6506 : if (!getChar(&c))
1828 0 : goto error;
1829 :
1830 3358 : if (c == '\\') {
1831 243 : if (!tokenbuf.append(c))
1832 0 : goto error;
1833 243 : if (!getChar(&c))
1834 0 : goto error;
1835 3115 : } else if (c == '[') {
1836 80 : inCharClass = true;
1837 3035 : } else if (c == ']') {
1838 80 : inCharClass = false;
1839 2955 : } else if (c == '/' && !inCharClass) {
1840 : // For compat with IE, allow unescaped / in char classes.
1841 210 : break;
1842 : }
1843 3148 : if (c == '\n' || c == EOF) {
1844 0 : ungetChar(c);
1845 0 : reportError(JSMSG_UNTERMINATED_REGEXP);
1846 0 : goto error;
1847 : }
1848 3148 : if (!tokenbuf.append(c))
1849 0 : goto error;
1850 : } while (true);
1851 :
1852 210 : RegExpFlag reflags = NoFlags;
1853 210 : unsigned length = tokenbuf.length() + 1;
1854 : while (true) {
1855 488 : if (!peekChar(&c))
1856 0 : goto error;
1857 349 : if (c == 'g' && !(reflags & GlobalFlag))
1858 86 : reflags = RegExpFlag(reflags | GlobalFlag);
1859 263 : else if (c == 'i' && !(reflags & IgnoreCaseFlag))
1860 50 : reflags = RegExpFlag(reflags | IgnoreCaseFlag);
1861 213 : else if (c == 'm' && !(reflags & MultilineFlag))
1862 3 : reflags = RegExpFlag(reflags | MultilineFlag);
1863 210 : else if (c == 'y' && !(reflags & StickyFlag))
1864 0 : reflags = RegExpFlag(reflags | StickyFlag);
1865 210 : else if (c == 'u' && !(reflags & UnicodeFlag))
1866 0 : reflags = RegExpFlag(reflags | UnicodeFlag);
1867 : else
1868 : break;
1869 139 : if (!getChar(&c))
1870 0 : goto error;
1871 139 : length++;
1872 : }
1873 :
1874 210 : if (!peekChar(&c))
1875 0 : goto error;
1876 210 : if (JS7_ISLET(c)) {
1877 0 : char buf[2] = { '\0', '\0' };
1878 0 : tp->pos.begin += length + 1;
1879 0 : buf[0] = char(c);
1880 0 : reportError(JSMSG_BAD_REGEXP_FLAG, buf);
1881 0 : consumeKnownChar(c);
1882 0 : goto error;
1883 : }
1884 210 : tp->type = TOK_REGEXP;
1885 210 : tp->setRegExpFlags(reflags);
1886 210 : goto out;
1887 : }
1888 :
1889 94 : tp->type = matchChar('=') ? TOK_DIVASSIGN : TOK_DIV;
1890 94 : goto out;
1891 :
1892 : case '%':
1893 16 : tp->type = matchChar('=') ? TOK_MODASSIGN : TOK_MOD;
1894 16 : goto out;
1895 :
1896 : case '-':
1897 843 : if (matchChar('-')) {
1898 72 : if (options().allowHTMLComments && !flags.isDirtyLine) {
1899 : int32_t c2;
1900 4 : if (!peekChar(&c2))
1901 0 : goto error;
1902 :
1903 4 : if (c2 == '>')
1904 0 : goto skipline;
1905 : }
1906 :
1907 72 : tp->type = TOK_DEC;
1908 : } else {
1909 771 : tp->type = matchChar('=') ? TOK_SUBASSIGN : TOK_SUB;
1910 : }
1911 843 : goto out;
1912 :
1913 : badchar:
1914 : default:
1915 0 : reportError(JSMSG_ILLEGAL_CHARACTER);
1916 0 : goto error;
1917 : }
1918 :
1919 : MOZ_CRASH("should have jumped to |out| or |error|");
1920 :
1921 : out:
1922 555417 : flags.isDirtyLine = true;
1923 555417 : tp->pos.end = userbuf.offset();
1924 : #ifdef DEBUG
1925 : // Save the modifier used to get this token, so that if an ungetToken()
1926 : // occurs and then the token is re-gotten (or peeked, etc.), we can assert
1927 : // that both gets have used the same modifiers.
1928 555417 : tp->modifier = modifier;
1929 555417 : tp->modifierException = NoException;
1930 : #endif
1931 555417 : MOZ_ASSERT(IsTokenSane(tp));
1932 555417 : *ttp = tp->type;
1933 555417 : return true;
1934 :
1935 : error:
1936 : // We didn't get a token, so don't set |flags.isDirtyLine|. And don't
1937 : // poison any of |*tp|: if we haven't allocated a token, |tp| could be
1938 : // uninitialized.
1939 0 : flags.hadError = true;
1940 : #ifdef DEBUG
1941 : // Poisoning userbuf on error establishes an invariant: once an erroneous
1942 : // token has been seen, userbuf will not be consulted again. This is true
1943 : // because the parser will deal with the illegal token by aborting parsing
1944 : // immediately.
1945 0 : userbuf.poison();
1946 : #endif
1947 : MOZ_MAKE_MEM_UNDEFINED(ttp, sizeof(*ttp));
1948 0 : return false;
1949 : }
1950 :
1951 : bool
1952 21832 : TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
1953 : {
1954 : int c;
1955 21832 : int nc = -1;
1956 :
1957 21832 : bool parsingTemplate = (untilChar == '`');
1958 :
1959 21832 : *tp = newToken(-1);
1960 21832 : tokenbuf.clear();
1961 :
1962 : // We need to detect any of these chars: " or ', \n (or its
1963 : // equivalents), \\, EOF. Because we detect EOL sequences here and
1964 : // put them back immediately, we can use getCharIgnoreEOL().
1965 691160 : while ((c = getCharIgnoreEOL()) != untilChar) {
1966 335329 : if (c == EOF) {
1967 0 : ungetCharIgnoreEOL(c);
1968 0 : error(JSMSG_UNTERMINATED_STRING);
1969 0 : return false;
1970 : }
1971 :
1972 335329 : if (c == '\\') {
1973 : // When parsing templates, we don't immediately report errors for
1974 : // invalid escapes; these are handled by the parser.
1975 : // In those cases we don't append to tokenbuf, since it won't be
1976 : // read.
1977 239 : if (!getChar(&c))
1978 0 : return false;
1979 :
1980 239 : switch (c) {
1981 0 : case 'b': c = '\b'; break;
1982 0 : case 'f': c = '\f'; break;
1983 173 : case 'n': c = '\n'; break;
1984 6 : case 'r': c = '\r'; break;
1985 10 : case 't': c = '\t'; break;
1986 0 : case 'v': c = '\v'; break;
1987 :
1988 : case '\n':
1989 : // ES5 7.8.4: an escaped line terminator represents
1990 : // no character.
1991 0 : continue;
1992 :
1993 : // Unicode character specification.
1994 : case 'u': {
1995 2 : uint32_t code = 0;
1996 :
1997 : int32_t c2;
1998 2 : if (!peekChar(&c2))
1999 0 : return false;
2000 :
2001 2 : uint32_t start = userbuf.offset() - 2;
2002 :
2003 2 : if (c2 == '{') {
2004 0 : consumeKnownChar('{');
2005 :
2006 0 : bool first = true;
2007 0 : bool valid = true;
2008 : do {
2009 0 : int32_t c = getCharIgnoreEOL();
2010 0 : if (c == EOF) {
2011 0 : if (parsingTemplate) {
2012 0 : setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
2013 0 : valid = false;
2014 0 : break;
2015 : }
2016 0 : reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
2017 0 : return false;
2018 : }
2019 0 : if (c == '}') {
2020 0 : if (first) {
2021 0 : if (parsingTemplate) {
2022 0 : setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
2023 0 : valid = false;
2024 0 : break;
2025 : }
2026 0 : reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
2027 0 : return false;
2028 : }
2029 0 : break;
2030 : }
2031 :
2032 0 : if (!JS7_ISHEX(c)) {
2033 0 : if (parsingTemplate) {
2034 : // We put the character back so that we read
2035 : // it on the next pass, which matters if it
2036 : // was '`' or '\'.
2037 0 : ungetCharIgnoreEOL(c);
2038 0 : setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
2039 0 : valid = false;
2040 0 : break;
2041 : }
2042 0 : reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
2043 0 : return false;
2044 : }
2045 :
2046 0 : code = (code << 4) | JS7_UNHEX(c);
2047 0 : if (code > unicode::NonBMPMax) {
2048 0 : if (parsingTemplate) {
2049 0 : setInvalidTemplateEscape(start + 3, InvalidEscapeType::UnicodeOverflow);
2050 0 : valid = false;
2051 0 : break;
2052 : }
2053 0 : reportInvalidEscapeError(start + 3, InvalidEscapeType::UnicodeOverflow);
2054 0 : return false;
2055 : }
2056 :
2057 0 : first = false;
2058 : } while (true);
2059 :
2060 0 : if (!valid)
2061 0 : continue;
2062 :
2063 0 : MOZ_ASSERT(code <= unicode::NonBMPMax);
2064 0 : if (code < unicode::NonBMPMin) {
2065 0 : c = code;
2066 : } else {
2067 0 : if (!tokenbuf.append(unicode::LeadSurrogate(code)))
2068 0 : return false;
2069 0 : c = unicode::TrailSurrogate(code);
2070 : }
2071 2 : break;
2072 : }
2073 :
2074 : CharT cp[4];
2075 6 : if (peekChars(4, cp) &&
2076 4 : JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]) && JS7_ISHEX(cp[3]))
2077 : {
2078 2 : c = JS7_UNHEX(cp[0]);
2079 2 : c = (c << 4) + JS7_UNHEX(cp[1]);
2080 2 : c = (c << 4) + JS7_UNHEX(cp[2]);
2081 2 : c = (c << 4) + JS7_UNHEX(cp[3]);
2082 2 : skipChars(4);
2083 : } else {
2084 0 : if (parsingTemplate) {
2085 0 : setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
2086 0 : continue;
2087 : }
2088 0 : reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
2089 0 : return false;
2090 : }
2091 2 : break;
2092 : }
2093 :
2094 : // Hexadecimal character specification.
2095 : case 'x': {
2096 : CharT cp[2];
2097 0 : if (peekChars(2, cp) && JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1])) {
2098 0 : c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]);
2099 0 : skipChars(2);
2100 : } else {
2101 0 : uint32_t start = userbuf.offset() - 2;
2102 0 : if (parsingTemplate) {
2103 0 : setInvalidTemplateEscape(start, InvalidEscapeType::Hexadecimal);
2104 0 : continue;
2105 : }
2106 0 : reportInvalidEscapeError(start, InvalidEscapeType::Hexadecimal);
2107 0 : return false;
2108 : }
2109 0 : break;
2110 : }
2111 :
2112 : default:
2113 : // Octal character specification.
2114 48 : if (JS7_ISOCT(c)) {
2115 5 : int32_t val = JS7_UNOCT(c);
2116 :
2117 5 : if (!peekChar(&c))
2118 0 : return false;
2119 :
2120 : // Strict mode code allows only \0, then a non-digit.
2121 5 : if (val != 0 || JS7_ISDEC(c)) {
2122 0 : if (parsingTemplate) {
2123 0 : setInvalidTemplateEscape(userbuf.offset() - 2, InvalidEscapeType::Octal);
2124 0 : continue;
2125 : }
2126 0 : if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
2127 0 : return false;
2128 0 : flags.sawOctalEscape = true;
2129 : }
2130 :
2131 5 : if (JS7_ISOCT(c)) {
2132 0 : val = 8 * val + JS7_UNOCT(c);
2133 0 : consumeKnownChar(c);
2134 0 : if (!peekChar(&c))
2135 0 : return false;
2136 0 : if (JS7_ISOCT(c)) {
2137 0 : int32_t save = val;
2138 0 : val = 8 * val + JS7_UNOCT(c);
2139 0 : if (val <= 0xFF)
2140 0 : consumeKnownChar(c);
2141 : else
2142 0 : val = save;
2143 : }
2144 : }
2145 :
2146 5 : c = char16_t(val);
2147 : }
2148 48 : break;
2149 : }
2150 335090 : } else if (TokenBuf::isRawEOLChar(c)) {
2151 9 : if (!parsingTemplate) {
2152 0 : ungetCharIgnoreEOL(c);
2153 0 : error(JSMSG_UNTERMINATED_STRING);
2154 0 : return false;
2155 : }
2156 9 : if (c == '\r') {
2157 0 : c = '\n';
2158 0 : if (userbuf.peekRawChar() == '\n')
2159 0 : skipCharsIgnoreEOL(1);
2160 : }
2161 :
2162 9 : if (!updateLineInfoForEOL())
2163 0 : return false;
2164 :
2165 9 : updateFlagsForEOL();
2166 335081 : } else if (parsingTemplate && c == '$') {
2167 665 : if ((nc = getCharIgnoreEOL()) == '{')
2168 665 : break;
2169 0 : ungetCharIgnoreEOL(nc);
2170 : }
2171 :
2172 334664 : if (!tokenbuf.append(c)) {
2173 0 : ReportOutOfMemory(cx);
2174 0 : return false;
2175 : }
2176 : }
2177 :
2178 21832 : JSAtom* atom = atomize(cx, tokenbuf);
2179 21832 : if (!atom)
2180 0 : return false;
2181 :
2182 21832 : if (!parsingTemplate) {
2183 20856 : (*tp)->type = TOK_STRING;
2184 : } else {
2185 976 : if (c == '$' && nc == '{')
2186 665 : (*tp)->type = TOK_TEMPLATE_HEAD;
2187 : else
2188 311 : (*tp)->type = TOK_NO_SUBS_TEMPLATE;
2189 : }
2190 :
2191 21832 : (*tp)->setAtom(atom);
2192 21832 : return true;
2193 : }
2194 :
2195 : const char*
2196 0 : TokenKindToDesc(TokenKind tt)
2197 : {
2198 0 : switch (tt) {
2199 : #define EMIT_CASE(name, desc) case TOK_##name: return desc;
2200 0 : FOR_EACH_TOKEN_KIND(EMIT_CASE)
2201 : #undef EMIT_CASE
2202 : case TOK_LIMIT:
2203 0 : MOZ_ASSERT_UNREACHABLE("TOK_LIMIT should not be passed.");
2204 : break;
2205 : }
2206 :
2207 0 : return "<bad TokenKind>";
2208 : }
2209 :
2210 : #ifdef DEBUG
2211 : const char*
2212 0 : TokenKindToString(TokenKind tt)
2213 : {
2214 0 : switch (tt) {
2215 : #define EMIT_CASE(name, desc) case TOK_##name: return "TOK_" #name;
2216 0 : FOR_EACH_TOKEN_KIND(EMIT_CASE)
2217 : #undef EMIT_CASE
2218 0 : case TOK_LIMIT: break;
2219 : }
2220 :
2221 0 : return "<bad TokenKind>";
2222 : }
2223 : #endif
2224 :
2225 : } // namespace frontend
2226 :
2227 : } // namespace js
2228 :
2229 :
2230 : JS_FRIEND_API(int)
2231 0 : js_fgets(char* buf, int size, FILE* file)
2232 : {
2233 : int n, i, c;
2234 : bool crflag;
2235 :
2236 0 : n = size - 1;
2237 0 : if (n < 0)
2238 0 : return -1;
2239 :
2240 0 : crflag = false;
2241 0 : for (i = 0; i < n && (c = fast_getc(file)) != EOF; i++) {
2242 0 : buf[i] = c;
2243 0 : if (c == '\n') { // any \n ends a line
2244 0 : i++; // keep the \n; we know there is room for \0
2245 0 : break;
2246 : }
2247 0 : if (crflag) { // \r not followed by \n ends line at the \r
2248 0 : ungetc(c, file);
2249 0 : break; // and overwrite c in buf with \0
2250 : }
2251 0 : crflag = (c == '\r');
2252 : }
2253 :
2254 0 : buf[i] = '\0';
2255 0 : return i;
2256 : }
|