From 860efa70ee5be805cd0d8c9bcfcb2374f11296f2 Mon Sep 17 00:00:00 2001 From: trav90 Date: Sun, 4 Mar 2018 15:05:47 -0600 Subject: Disable strict-aliasing for GCC in js/src Compiling our tree with strict-aliasing is not supported. --- js/src/moz.build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/moz.build b/js/src/moz.build index 1162cb70c..0a4abd80f 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -785,7 +785,9 @@ if CONFIG['JS_HAS_CTYPES']: DEFINES['FFI_BUILDING'] = True if CONFIG['GNU_CXX']: - CXXFLAGS += ['-Wno-shadow', '-Werror=format'] + # Disable strict-aliasing for GCC, which is enabled by default + # starting with version 7.1, see Mozilla bug 1363009. + CXXFLAGS += ['-Wno-shadow', '-Werror=format', '-fno-strict-aliasing'] # Suppress warnings in third-party code. if CONFIG['CLANG_CXX']: -- cgit v1.2.3 From d219b672a30830953d6b26399e395f75f23cd33e Mon Sep 17 00:00:00 2001 From: trav90 Date: Sun, 4 Mar 2018 15:07:25 -0600 Subject: Disable strict-aliasing for GCC in js/src/gdb Compiling our tree with strict-aliasing is not supported. --- js/src/gdb/moz.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/gdb/moz.build b/js/src/gdb/moz.build index 07047fb74..681f9807c 100644 --- a/js/src/gdb/moz.build +++ b/js/src/gdb/moz.build @@ -42,7 +42,7 @@ if CONFIG['ENABLE_INTL_API'] and CONFIG['MOZ_ICU_DATA_ARCHIVE']: OS_LIBS += CONFIG['MOZ_ZLIB_LIBS'] if CONFIG['GNU_CXX']: - CXXFLAGS += ['-Wno-shadow'] + CXXFLAGS += ['-Wno-shadow', '-fno-strict-aliasing'] # This is intended as a temporary workaround to enable VS2015. if CONFIG['_MSC_VER']: -- cgit v1.2.3 From b9bb9f4cc643bf2285ee8396cb82bcc86ddfa693 Mon Sep 17 00:00:00 2001 From: trav90 Date: Sun, 4 Mar 2018 15:09:21 -0600 Subject: Disable -Wimplicit-fallthrough when building jsdtoa.cpp GCC 7 supports the clang option -Wimplicit-fallthrough. --- js/src/moz.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/moz.build b/js/src/moz.build index 0a4abd80f..77acb10b9 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -790,5 +790,5 @@ if CONFIG['GNU_CXX']: CXXFLAGS += ['-Wno-shadow', '-Werror=format', '-fno-strict-aliasing'] # Suppress warnings in third-party code. -if CONFIG['CLANG_CXX']: +if CONFIG['CLANG_CXX'] or CONFIG['GNU_CXX']: SOURCES['jsdtoa.cpp'].flags += ['-Wno-implicit-fallthrough'] -- cgit v1.2.3 From 141607b6de3113e5bb0174ee817bf9d438e31950 Mon Sep 17 00:00:00 2001 From: trav90 Date: Sun, 4 Mar 2018 15:30:09 -0600 Subject: Disable strict-aliasing for GCC in js/src/jsapi-tests Compiling our tree with strict-aliasing is not supported. --- js/src/jsapi-tests/moz.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index ab42ff384..f7a6080aa 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -147,7 +147,7 @@ USE_LIBS += [ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS'] if CONFIG['GNU_CXX']: - CXXFLAGS += ['-Wno-shadow', '-Werror=format'] + CXXFLAGS += ['-Wno-shadow', '-Werror=format', '-fno-strict-aliasing'] # This is intended as a temporary workaround to enable VS2015. if CONFIG['_MSC_VER']: -- cgit v1.2.3 From 93f8e06bb8d8656e868679d584c7c8771ff8e42f Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 17 Aug 2017 20:37:46 +0200 Subject: JS - RegExp - match updated spec for `/\b/iu` and `/\B/iu` --- js/src/irregexp/RegExpEngine.cpp | 25 ++++++++++++++++------ js/src/irregexp/RegExpEngine.h | 8 +++++-- .../RegExp/unicode-ignoreCase-word-boundary.js | 25 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js (limited to 'js/src') diff --git a/js/src/irregexp/RegExpEngine.cpp b/js/src/irregexp/RegExpEngine.cpp index 2e19065fd..7116ff1e5 100644 --- a/js/src/irregexp/RegExpEngine.cpp +++ b/js/src/irregexp/RegExpEngine.cpp @@ -2358,7 +2358,10 @@ void BoyerMoorePositionInfo::SetInterval(const Interval& interval) { s_ = AddRange(s_, kSpaceRanges, kSpaceRangeCount, interval); - w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); + if (unicode_ignore_case_) + w_ = AddRange(w_, kIgnoreCaseWordRanges, kIgnoreCaseWordRangeCount, interval); + else + w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); d_ = AddRange(d_, kDigitRanges, kDigitRangeCount, interval); surrogate_ = AddRange(surrogate_, kSurrogateRanges, kSurrogateRangeCount, interval); @@ -2395,11 +2398,12 @@ BoyerMoorePositionInfo::SetAll() BoyerMooreLookahead::BoyerMooreLookahead(LifoAlloc* alloc, size_t length, RegExpCompiler* compiler) : length_(length), compiler_(compiler), bitmaps_(*alloc) { + bool unicode_ignore_case = compiler->unicode() && compiler->ignore_case(); max_char_ = MaximumCharacter(compiler->ascii()); bitmaps_.reserve(length); for (size_t i = 0; i < length; i++) - bitmaps_.append(alloc->newInfallible(alloc)); + bitmaps_.append(alloc->newInfallible(alloc, unicode_ignore_case)); } // Find the longest range of lookahead that has the fewest number of different @@ -3065,15 +3069,22 @@ EmitNotInSurrogatePair(RegExpCompiler* compiler, RegExpNode* on_success, Trace* // Check for [0-9A-Z_a-z]. static void EmitWordCheck(RegExpMacroAssembler* assembler, - jit::Label* word, jit::Label* non_word, bool fall_through_on_word) + jit::Label* word, jit::Label* non_word, bool fall_through_on_word, + bool unicode_ignore_case) { - if (assembler->CheckSpecialCharacterClass(fall_through_on_word ? 'w' : 'W', + if (!unicode_ignore_case && + assembler->CheckSpecialCharacterClass(fall_through_on_word ? 'w' : 'W', fall_through_on_word ? non_word : word)) { // Optimized implementation available. return; } + if (unicode_ignore_case) { + assembler->CheckCharacter(0x017F, word); + assembler->CheckCharacter(0x212A, word); + } + assembler->CheckCharacterGT('z', non_word); assembler->CheckCharacterLT('0', non_word); assembler->CheckCharacterGT('a' - 1, word); @@ -3122,7 +3133,8 @@ AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace) assembler->LoadCurrentCharacter(trace->cp_offset(), &before_non_word); } // Fall through on non-word. - EmitWordCheck(assembler, &before_word, &before_non_word, false); + EmitWordCheck(assembler, &before_word, &before_non_word, false, + compiler->unicode() && compiler->ignore_case()); // Next character is not a word character. assembler->Bind(&before_non_word); jit::Label ok; @@ -3162,7 +3174,8 @@ AssertionNode::BacktrackIfPrevious(RegExpCompiler* compiler, // We already checked that we are not at the start of input so it must be // OK to load the previous character. assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, &dummy, false); - EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord); + EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord, + compiler->unicode() && compiler->ignore_case()); assembler->Bind(&fall_through); on_success()->Emit(compiler, &new_trace); diff --git a/js/src/irregexp/RegExpEngine.h b/js/src/irregexp/RegExpEngine.h index 78c784aaf..1a8fd4b22 100644 --- a/js/src/irregexp/RegExpEngine.h +++ b/js/src/irregexp/RegExpEngine.h @@ -1195,13 +1195,14 @@ AddRange(ContainedInLattice a, class BoyerMoorePositionInfo { public: - explicit BoyerMoorePositionInfo(LifoAlloc* alloc) + explicit BoyerMoorePositionInfo(LifoAlloc* alloc, bool unicode_ignore_case) : map_(*alloc), map_count_(0), w_(kNotYet), s_(kNotYet), d_(kNotYet), - surrogate_(kNotYet) + surrogate_(kNotYet), + unicode_ignore_case_(unicode_ignore_case) { map_.reserve(kMapSize); for (int i = 0; i < kMapSize; i++) @@ -1228,6 +1229,9 @@ class BoyerMoorePositionInfo ContainedInLattice s_; // The \s character class. ContainedInLattice d_; // The \d character class. ContainedInLattice surrogate_; // Surrogate UTF-16 code units. + + // True if the RegExp has unicode and ignoreCase flags. + bool unicode_ignore_case_; }; typedef InfallibleVector BoyerMoorePositionInfoVector; diff --git a/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js b/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js new file mode 100644 index 000000000..c1a04bd3d --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js @@ -0,0 +1,25 @@ +var BUGNUMBER = 1338373; +var summary = "Word boundary should match U+017F and U+212A in unicode+ignoreCase."; + +assertEq(/\b/iu.test('\u017F'), true); +assertEq(/\b/i.test('\u017F'), false); +assertEq(/\b/u.test('\u017F'), false); +assertEq(/\b/.test('\u017F'), false); + +assertEq(/\b/iu.test('\u212A'), true); +assertEq(/\b/i.test('\u212A'), false); +assertEq(/\b/u.test('\u212A'), false); +assertEq(/\b/.test('\u212A'), false); + +assertEq(/\B/iu.test('\u017F'), false); +assertEq(/\B/i.test('\u017F'), true); +assertEq(/\B/u.test('\u017F'), true); +assertEq(/\B/.test('\u017F'), true); + +assertEq(/\B/iu.test('\u212A'), false); +assertEq(/\B/i.test('\u212A'), true); +assertEq(/\B/u.test('\u212A'), true); +assertEq(/\B/.test('\u212A'), true); + +if (typeof reportCompare === "function") + reportCompare(true, true); -- cgit v1.2.3 From c43c8f17629f2dde26f91d69f241fa7bd2165dab Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 24 Aug 2017 10:34:22 +0200 Subject: JS - Object - "TypeError: setting a property that has only a getter" without mentioning file and property name --- js/src/js.msg | 2 +- js/src/jsobj.cpp | 12 ------------ js/src/jsobj.h | 3 --- 3 files changed, 1 insertion(+), 16 deletions(-) (limited to 'js/src') diff --git a/js/src/js.msg b/js/src/js.msg index 246e363c3..cb5fc383b 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -65,7 +65,7 @@ MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER, 1, JSEXN_TYPEERR, "invalid {0} usage") MSG_DEF(JSMSG_BAD_ARRAY_LENGTH, 0, JSEXN_RANGEERR, "invalid array length") MSG_DEF(JSMSG_REDECLARED_VAR, 2, JSEXN_SYNTAXERR, "redeclaration of {0} {1}") MSG_DEF(JSMSG_UNDECLARED_VAR, 1, JSEXN_REFERENCEERR, "assignment to undeclared variable {0}") -MSG_DEF(JSMSG_GETTER_ONLY, 0, JSEXN_TYPEERR, "setting a property that has only a getter") +MSG_DEF(JSMSG_GETTER_ONLY, 1, JSEXN_TYPEERR, "setting getter-only property {0}") MSG_DEF(JSMSG_OVERWRITING_ACCESSOR, 1, JSEXN_TYPEERR, "can't overwrite accessor property {0}") MSG_DEF(JSMSG_UNDEFINED_PROP, 1, JSEXN_REFERENCEERR, "reference to undefined property {0}") MSG_DEF(JSMSG_INVALID_MAP_ITERABLE, 1, JSEXN_TYPEERR, "iterable for {0} should have array-like objects") diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index a39a4b0a0..33feb0a54 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3298,18 +3298,6 @@ GetObjectSlotNameFunctor::operator()(JS::CallbackTracer* trc, char* buf, size_t } } -bool -js::ReportGetterOnlyAssignment(JSContext* cx, bool strict) -{ - return JS_ReportErrorFlagsAndNumberASCII(cx, - strict - ? JSREPORT_ERROR - : JSREPORT_WARNING | JSREPORT_STRICT, - GetErrorMessage, nullptr, - JSMSG_GETTER_ONLY); -} - - /*** Debugging routines **************************************************************************/ #ifdef DEBUG diff --git a/js/src/jsobj.h b/js/src/jsobj.h index fbf4e47be..af79131af 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1320,9 +1320,6 @@ template bool XDRObjectLiteral(XDRState* xdr, MutableHandleObject obj); -extern bool -ReportGetterOnlyAssignment(JSContext* cx, bool strict); - /* * Report a TypeError: "so-and-so is not an object". * Using NotNullObject is usually less code. -- cgit v1.2.3 From 519775b8d9d823b8cee786bc668e050110a8aa67 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Mon, 12 Mar 2018 11:09:28 +0100 Subject: Revert "JS - RegExp - match updated spec for `/\b/iu` and `/\B/iu`" This reverts commit 93f8e06bb8d8656e868679d584c7c8771ff8e42f. --- js/src/irregexp/RegExpEngine.cpp | 25 ++++++---------------- js/src/irregexp/RegExpEngine.h | 8 ++----- .../RegExp/unicode-ignoreCase-word-boundary.js | 25 ---------------------- 3 files changed, 8 insertions(+), 50 deletions(-) delete mode 100644 js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js (limited to 'js/src') diff --git a/js/src/irregexp/RegExpEngine.cpp b/js/src/irregexp/RegExpEngine.cpp index 7116ff1e5..2e19065fd 100644 --- a/js/src/irregexp/RegExpEngine.cpp +++ b/js/src/irregexp/RegExpEngine.cpp @@ -2358,10 +2358,7 @@ void BoyerMoorePositionInfo::SetInterval(const Interval& interval) { s_ = AddRange(s_, kSpaceRanges, kSpaceRangeCount, interval); - if (unicode_ignore_case_) - w_ = AddRange(w_, kIgnoreCaseWordRanges, kIgnoreCaseWordRangeCount, interval); - else - w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); + w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); d_ = AddRange(d_, kDigitRanges, kDigitRangeCount, interval); surrogate_ = AddRange(surrogate_, kSurrogateRanges, kSurrogateRangeCount, interval); @@ -2398,12 +2395,11 @@ BoyerMoorePositionInfo::SetAll() BoyerMooreLookahead::BoyerMooreLookahead(LifoAlloc* alloc, size_t length, RegExpCompiler* compiler) : length_(length), compiler_(compiler), bitmaps_(*alloc) { - bool unicode_ignore_case = compiler->unicode() && compiler->ignore_case(); max_char_ = MaximumCharacter(compiler->ascii()); bitmaps_.reserve(length); for (size_t i = 0; i < length; i++) - bitmaps_.append(alloc->newInfallible(alloc, unicode_ignore_case)); + bitmaps_.append(alloc->newInfallible(alloc)); } // Find the longest range of lookahead that has the fewest number of different @@ -3069,22 +3065,15 @@ EmitNotInSurrogatePair(RegExpCompiler* compiler, RegExpNode* on_success, Trace* // Check for [0-9A-Z_a-z]. static void EmitWordCheck(RegExpMacroAssembler* assembler, - jit::Label* word, jit::Label* non_word, bool fall_through_on_word, - bool unicode_ignore_case) + jit::Label* word, jit::Label* non_word, bool fall_through_on_word) { - if (!unicode_ignore_case && - assembler->CheckSpecialCharacterClass(fall_through_on_word ? 'w' : 'W', + if (assembler->CheckSpecialCharacterClass(fall_through_on_word ? 'w' : 'W', fall_through_on_word ? non_word : word)) { // Optimized implementation available. return; } - if (unicode_ignore_case) { - assembler->CheckCharacter(0x017F, word); - assembler->CheckCharacter(0x212A, word); - } - assembler->CheckCharacterGT('z', non_word); assembler->CheckCharacterLT('0', non_word); assembler->CheckCharacterGT('a' - 1, word); @@ -3133,8 +3122,7 @@ AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace) assembler->LoadCurrentCharacter(trace->cp_offset(), &before_non_word); } // Fall through on non-word. - EmitWordCheck(assembler, &before_word, &before_non_word, false, - compiler->unicode() && compiler->ignore_case()); + EmitWordCheck(assembler, &before_word, &before_non_word, false); // Next character is not a word character. assembler->Bind(&before_non_word); jit::Label ok; @@ -3174,8 +3162,7 @@ AssertionNode::BacktrackIfPrevious(RegExpCompiler* compiler, // We already checked that we are not at the start of input so it must be // OK to load the previous character. assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, &dummy, false); - EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord, - compiler->unicode() && compiler->ignore_case()); + EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord); assembler->Bind(&fall_through); on_success()->Emit(compiler, &new_trace); diff --git a/js/src/irregexp/RegExpEngine.h b/js/src/irregexp/RegExpEngine.h index 1a8fd4b22..78c784aaf 100644 --- a/js/src/irregexp/RegExpEngine.h +++ b/js/src/irregexp/RegExpEngine.h @@ -1195,14 +1195,13 @@ AddRange(ContainedInLattice a, class BoyerMoorePositionInfo { public: - explicit BoyerMoorePositionInfo(LifoAlloc* alloc, bool unicode_ignore_case) + explicit BoyerMoorePositionInfo(LifoAlloc* alloc) : map_(*alloc), map_count_(0), w_(kNotYet), s_(kNotYet), d_(kNotYet), - surrogate_(kNotYet), - unicode_ignore_case_(unicode_ignore_case) + surrogate_(kNotYet) { map_.reserve(kMapSize); for (int i = 0; i < kMapSize; i++) @@ -1229,9 +1228,6 @@ class BoyerMoorePositionInfo ContainedInLattice s_; // The \s character class. ContainedInLattice d_; // The \d character class. ContainedInLattice surrogate_; // Surrogate UTF-16 code units. - - // True if the RegExp has unicode and ignoreCase flags. - bool unicode_ignore_case_; }; typedef InfallibleVector BoyerMoorePositionInfoVector; diff --git a/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js b/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js deleted file mode 100644 index c1a04bd3d..000000000 --- a/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js +++ /dev/null @@ -1,25 +0,0 @@ -var BUGNUMBER = 1338373; -var summary = "Word boundary should match U+017F and U+212A in unicode+ignoreCase."; - -assertEq(/\b/iu.test('\u017F'), true); -assertEq(/\b/i.test('\u017F'), false); -assertEq(/\b/u.test('\u017F'), false); -assertEq(/\b/.test('\u017F'), false); - -assertEq(/\b/iu.test('\u212A'), true); -assertEq(/\b/i.test('\u212A'), false); -assertEq(/\b/u.test('\u212A'), false); -assertEq(/\b/.test('\u212A'), false); - -assertEq(/\B/iu.test('\u017F'), false); -assertEq(/\B/i.test('\u017F'), true); -assertEq(/\B/u.test('\u017F'), true); -assertEq(/\B/.test('\u017F'), true); - -assertEq(/\B/iu.test('\u212A'), false); -assertEq(/\B/i.test('\u212A'), true); -assertEq(/\B/u.test('\u212A'), true); -assertEq(/\B/.test('\u212A'), true); - -if (typeof reportCompare === "function") - reportCompare(true, true); -- cgit v1.2.3 From b392e6d2ea60191615771900690b37b52b47bcd3 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Mon, 12 Mar 2018 14:15:56 +0100 Subject: Generate irregexp character tables with make_unicode.py. --- js/src/irregexp/RegExpCharacters-inl.h | 40 +++++++ js/src/irregexp/RegExpCharacters.cpp | 135 +++++++++++++++++++++ js/src/irregexp/RegExpCharacters.h | 90 ++++++++++++++ js/src/irregexp/RegExpEngine.cpp | 115 +----------------- js/src/moz.build | 1 + js/src/vm/make_unicode.py | 213 +++++++++++++++++++++++++++++++++ 6 files changed, 485 insertions(+), 109 deletions(-) create mode 100644 js/src/irregexp/RegExpCharacters-inl.h create mode 100644 js/src/irregexp/RegExpCharacters.cpp create mode 100644 js/src/irregexp/RegExpCharacters.h (limited to 'js/src') diff --git a/js/src/irregexp/RegExpCharacters-inl.h b/js/src/irregexp/RegExpCharacters-inl.h new file mode 100644 index 000000000..d001819fc --- /dev/null +++ b/js/src/irregexp/RegExpCharacters-inl.h @@ -0,0 +1,40 @@ +/* Generated by make_unicode.py DO NOT MODIFY */ +/* Unicode version: 9.0.0 */ +#ifndef V8_JSREGEXPCHARACTERS_INL_H_ +#define V8_JSREGEXPCHARACTERS_INL_H_ + +namespace js { + +namespace irregexp { + +static inline bool +RangeContainsLatin1Equivalents(CharacterRange range, bool unicode) +{ + if (unicode) { + // "LATIN SMALL LETTER LONG S" case folds to "LATIN SMALL LETTER S". + if (range.Contains(0x017F)) + return true; + // "LATIN CAPITAL LETTER SHARP S" case folds to "LATIN SMALL LETTER SHARP S". + if (range.Contains(0x1E9E)) + return true; + // "KELVIN SIGN" case folds to "LATIN SMALL LETTER K". + if (range.Contains(0x212A)) + return true; + // "ANGSTROM SIGN" case folds to "LATIN SMALL LETTER A WITH RING ABOVE". + if (range.Contains(0x212B)) + return true; + } + + // "GREEK CAPITAL LETTER MU" case maps to "MICRO SIGN". + // "GREEK SMALL LETTER MU" case maps to "MICRO SIGN". + if (range.Contains(0x039C) || range.Contains(0x03BC)) + return true; + // "LATIN CAPITAL LETTER Y WITH DIAERESIS" case maps to "LATIN SMALL LETTER Y WITH DIAERESIS". + if (range.Contains(0x0178)) + return true; + return false; +} + +} } // namespace js::irregexp + +#endif // V8_JSREGEXPCHARACTERS_INL_H_ diff --git a/js/src/irregexp/RegExpCharacters.cpp b/js/src/irregexp/RegExpCharacters.cpp new file mode 100644 index 000000000..096c02760 --- /dev/null +++ b/js/src/irregexp/RegExpCharacters.cpp @@ -0,0 +1,135 @@ +/* Generated by make_unicode.py DO NOT MODIFY */ +/* Unicode version: 9.0.0 */ +#include "irregexp/RegExpCharacters.h" + +#include "mozilla/Assertions.h" + +char16_t +js::irregexp::ConvertNonLatin1ToLatin1(char16_t c, bool unicode) +{ + MOZ_ASSERT(c > 0xFF, "Character mustn't be Latin1"); + if (unicode) { + // "LATIN SMALL LETTER LONG S" case folds to "LATIN SMALL LETTER S". + if (c == 0x017F) + return 0x73; + // "LATIN CAPITAL LETTER SHARP S" case folds to "LATIN SMALL LETTER SHARP S". + if (c == 0x1E9E) + return 0xDF; + // "KELVIN SIGN" case folds to "LATIN SMALL LETTER K". + if (c == 0x212A) + return 0x6B; + // "ANGSTROM SIGN" case folds to "LATIN SMALL LETTER A WITH RING ABOVE". + if (c == 0x212B) + return 0xE5; + } + + // "GREEK CAPITAL LETTER MU" case maps to "MICRO SIGN". + // "GREEK SMALL LETTER MU" case maps to "MICRO SIGN". + if (c == 0x039C || c == 0x03BC) + return 0xB5; + // "LATIN CAPITAL LETTER Y WITH DIAERESIS" case maps to "LATIN SMALL LETTER Y WITH DIAERESIS". + if (c == 0x0178) + return 0xFF; + return 0; +} + +const int js::irregexp::kSpaceRanges[] = { + 0x0009, 0x000D + 1, // CHARACTER TABULATION..CARRIAGE RETURN (CR) + 0x0020, 0x0020 + 1, // SPACE + 0x00A0, 0x00A0 + 1, // NO-BREAK SPACE + 0x1680, 0x1680 + 1, // OGHAM SPACE MARK + 0x2000, 0x200A + 1, // EN QUAD..HAIR SPACE + 0x2028, 0x2029 + 1, // LINE SEPARATOR..PARAGRAPH SEPARATOR + 0x202F, 0x202F + 1, // NARROW NO-BREAK SPACE + 0x205F, 0x205F + 1, // MEDIUM MATHEMATICAL SPACE + 0x3000, 0x3000 + 1, // IDEOGRAPHIC SPACE + 0xFEFF, 0xFEFF + 1, // ZERO WIDTH NO-BREAK SPACE + 0xFFFF + 1 +}; +const int js::irregexp::kSpaceRangeCount = 21; + +const int js::irregexp::kSpaceAndSurrogateRanges[] = { + 0x0009, 0x000D + 1, // CHARACTER TABULATION..CARRIAGE RETURN (CR) + 0x0020, 0x0020 + 1, // SPACE + 0x00A0, 0x00A0 + 1, // NO-BREAK SPACE + 0x1680, 0x1680 + 1, // OGHAM SPACE MARK + 0x2000, 0x200A + 1, // EN QUAD..HAIR SPACE + 0x2028, 0x2029 + 1, // LINE SEPARATOR..PARAGRAPH SEPARATOR + 0x202F, 0x202F + 1, // NARROW NO-BREAK SPACE + 0x205F, 0x205F + 1, // MEDIUM MATHEMATICAL SPACE + 0x3000, 0x3000 + 1, // IDEOGRAPHIC SPACE + 0xD800, 0xDFFF + 1, // .. + 0xFEFF, 0xFEFF + 1, // ZERO WIDTH NO-BREAK SPACE + 0xFFFF + 1 +}; +const int js::irregexp::kSpaceAndSurrogateRangeCount = 23; + +const int js::irregexp::kWordRanges[] = { + 0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE + 0x0041, 0x005A + 1, // LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z + 0x005F, 0x005F + 1, // LOW LINE + 0x0061, 0x007A + 1, // LATIN SMALL LETTER A..LATIN SMALL LETTER Z + 0xFFFF + 1 +}; +const int js::irregexp::kWordRangeCount = 9; + +const int js::irregexp::kIgnoreCaseWordRanges[] = { + 0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE + 0x0041, 0x005A + 1, // LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z + 0x005F, 0x005F + 1, // LOW LINE + 0x0061, 0x007A + 1, // LATIN SMALL LETTER A..LATIN SMALL LETTER Z + 0x017F, 0x017F + 1, // LATIN SMALL LETTER LONG S + 0x212A, 0x212A + 1, // KELVIN SIGN + 0xFFFF + 1 +}; +const int js::irregexp::kIgnoreCaseWordRangeCount = 13; + +const int js::irregexp::kWordAndSurrogateRanges[] = { + 0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE + 0x0041, 0x005A + 1, // LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z + 0x005F, 0x005F + 1, // LOW LINE + 0x0061, 0x007A + 1, // LATIN SMALL LETTER A..LATIN SMALL LETTER Z + 0xD800, 0xDFFF + 1, // .. + 0xFFFF + 1 +}; +const int js::irregexp::kWordAndSurrogateRangeCount = 11; + +const int js::irregexp::kNegatedIgnoreCaseWordAndSurrogateRanges[] = { + 0x0000, 0x002F + 1, // NULL..SOLIDUS + 0x003A, 0x0040 + 1, // COLON..COMMERCIAL AT + 0x005B, 0x005E + 1, // LEFT SQUARE BRACKET..CIRCUMFLEX ACCENT + 0x0060, 0x0060 + 1, // GRAVE ACCENT + 0x007B, 0x017E + 1, // LEFT CURLY BRACKET..LATIN SMALL LETTER Z WITH CARON + 0x0180, 0x2129 + 1, // LATIN SMALL LETTER B WITH STROKE..TURNED GREEK SMALL LETTER IOTA + 0x212B, 0xD7FF + 1, // ANGSTROM SIGN.. + 0xE000, 0xFFFF + 1, // Private Use.. + 0xFFFF + 1 +}; +const int js::irregexp::kNegatedIgnoreCaseWordAndSurrogateRangeCount = 17; + +const int js::irregexp::kDigitRanges[] = { + 0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE + 0xFFFF + 1 +}; +const int js::irregexp::kDigitRangeCount = 3; + +const int js::irregexp::kDigitAndSurrogateRanges[] = { + 0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE + 0xD800, 0xDFFF + 1, // .. + 0xFFFF + 1 +}; +const int js::irregexp::kDigitAndSurrogateRangeCount = 5; + +const int js::irregexp::kSurrogateRanges[] = { + 0xD800, 0xDFFF + 1, // .. + 0xFFFF + 1 +}; +const int js::irregexp::kSurrogateRangeCount = 3; + +const int js::irregexp::kLineTerminatorRanges[] = { + 0x000A, 0x000A + 1, // LINE FEED (LF) + 0x000D, 0x000D + 1, // CARRIAGE RETURN (CR) + 0x2028, 0x2029 + 1, // LINE SEPARATOR..PARAGRAPH SEPARATOR + 0xFFFF + 1 +}; +const int js::irregexp::kLineTerminatorRangeCount = 7; diff --git a/js/src/irregexp/RegExpCharacters.h b/js/src/irregexp/RegExpCharacters.h new file mode 100644 index 000000000..0d3cf096f --- /dev/null +++ b/js/src/irregexp/RegExpCharacters.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ + +// Copyright 2012 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef V8_JSREGEXPCHARACTERS_H_ +#define V8_JSREGEXPCHARACTERS_H_ + +namespace js { + +namespace irregexp { + +char16_t +ConvertNonLatin1ToLatin1(char16_t c, bool unicode); + +// ------------------------------------------------------------------- +// CharacterRange + +// The ranges have inclusive from and exclusive to. + +// This covers \s as defined in ES2016, 21.2.2.12 CharacterClassEscape, +// which includes WhiteSpace (11.2) and LineTerminator (11.3) values. +extern const int kSpaceRanges[]; +extern const int kSpaceRangeCount; + +// Characters in \s and additionally all surrogate characters. +extern const int kSpaceAndSurrogateRanges[]; +extern const int kSpaceAndSurrogateRangeCount; + +// This covers \w as defined in ES2016, 21.2.2.12 CharacterClassEscape. +extern const int kWordRanges[]; +extern const int kWordRangeCount; + +// Characters which case-fold to characters in \w. +extern const int kIgnoreCaseWordRanges[]; +extern const int kIgnoreCaseWordRangeCount; + +// Characters in \w and additionally all surrogate characters. +extern const int kWordAndSurrogateRanges[]; +extern const int kWordAndSurrogateRangeCount; + +// All characters excluding those which case-fold to \w and excluding all +// surrogate characters. +extern const int kNegatedIgnoreCaseWordAndSurrogateRanges[]; +extern const int kNegatedIgnoreCaseWordAndSurrogateRangeCount; + +// This covers \d as defined in ES2016, 21.2.2.12 CharacterClassEscape. +extern const int kDigitRanges[]; +extern const int kDigitRangeCount; + +// Characters in \d and additionally all surrogate characters. +extern const int kDigitAndSurrogateRanges[]; +extern const int kDigitAndSurrogateRangeCount; + +// The range of all surrogate characters. +extern const int kSurrogateRanges[]; +extern const int kSurrogateRangeCount; + +// Line terminators as defined in ES2016, 11.3 LineTerminator. +extern const int kLineTerminatorRanges[]; +extern const int kLineTerminatorRangeCount; + +} } // namespace js::irregexp + +#endif // V8_JSREGEXPCHARACTERS_H_ diff --git a/js/src/irregexp/RegExpEngine.cpp b/js/src/irregexp/RegExpEngine.cpp index 2e19065fd..0011e976f 100644 --- a/js/src/irregexp/RegExpEngine.cpp +++ b/js/src/irregexp/RegExpEngine.cpp @@ -31,10 +31,14 @@ #include "irregexp/RegExpEngine.h" #include "irregexp/NativeRegExpMacroAssembler.h" +#include "irregexp/RegExpCharacters.h" #include "irregexp/RegExpMacroAssembler.h" #include "jit/ExecutableAllocator.h" #include "jit/JitCommon.h" +// Generated table +#include "irregexp/RegExpCharacters-inl.h" + using namespace js; using namespace js::irregexp; @@ -61,61 +65,6 @@ RegExpNode::RegExpNode(LifoAlloc* alloc) bm_info_[0] = bm_info_[1] = nullptr; } -// ------------------------------------------------------------------- -// CharacterRange - -// The '2' variant has inclusive from and exclusive to. -// This covers \s as defined in ECMA-262 5.1, 15.10.2.12, -// which include WhiteSpace (7.2) or LineTerminator (7.3) values. -static const int kSpaceRanges[] = { '\t', '\r' + 1, ' ', ' ' + 1, - 0x00A0, 0x00A1, 0x1680, 0x1681, 0x180E, 0x180F, 0x2000, 0x200B, - 0x2028, 0x202A, 0x202F, 0x2030, 0x205F, 0x2060, 0x3000, 0x3001, - 0xFEFF, 0xFF00, 0x10000 }; -static const int kSpaceRangeCount = ArrayLength(kSpaceRanges); - -static const int kSpaceAndSurrogateRanges[] = { '\t', '\r' + 1, ' ', ' ' + 1, - 0x00A0, 0x00A1, 0x1680, 0x1681, 0x180E, 0x180F, 0x2000, 0x200B, - 0x2028, 0x202A, 0x202F, 0x2030, 0x205F, 0x2060, 0x3000, 0x3001, - unicode::LeadSurrogateMin, unicode::TrailSurrogateMax + 1, - 0xFEFF, 0xFF00, 0x10000 }; -static const int kSpaceAndSurrogateRangeCount = ArrayLength(kSpaceAndSurrogateRanges); -static const int kWordRanges[] = { - '0', '9' + 1, 'A', 'Z' + 1, '_', '_' + 1, 'a', 'z' + 1, 0x10000 }; -static const int kWordRangeCount = ArrayLength(kWordRanges); -static const int kIgnoreCaseWordRanges[] = { - '0', '9' + 1, 'A', 'Z' + 1, '_', '_' + 1, 'a', 'z' + 1, - 0x017F, 0x017F + 1, 0x212A, 0x212A + 1, - 0x10000 }; -static const int kIgnoreCaseWordCount = ArrayLength(kIgnoreCaseWordRanges); -static const int kWordAndSurrogateRanges[] = { - '0', '9' + 1, 'A', 'Z' + 1, '_', '_' + 1, 'a', 'z' + 1, - unicode::LeadSurrogateMin, unicode::TrailSurrogateMax + 1, - 0x10000 }; -static const int kWordAndSurrogateRangeCount = ArrayLength(kWordAndSurrogateRanges); -static const int kNegatedIgnoreCaseWordAndSurrogateRanges[] = { - 0, '0', '9' + 1, 'A', - 'Z' + 1, '_', '_' + 1, 'a', - 'z' + 1, 0x017F, - 0x017F + 1, 0x212A, - 0x212A + 1, unicode::LeadSurrogateMin, - unicode::TrailSurrogateMax + 1, 0x10000, - 0x10000 }; -static const int kNegatedIgnoreCaseWordAndSurrogateRangeCount = - ArrayLength(kNegatedIgnoreCaseWordAndSurrogateRanges); -static const int kDigitRanges[] = { '0', '9' + 1, 0x10000 }; -static const int kDigitRangeCount = ArrayLength(kDigitRanges); -static const int kDigitAndSurrogateRanges[] = { - '0', '9' + 1, - unicode::LeadSurrogateMin, unicode::TrailSurrogateMax + 1, - 0x10000 }; -static const int kDigitAndSurrogateRangeCount = ArrayLength(kDigitAndSurrogateRanges); -static const int kSurrogateRanges[] = { - unicode::LeadSurrogateMin, unicode::TrailSurrogateMax + 1, - 0x10000 }; -static const int kSurrogateRangeCount = ArrayLength(kSurrogateRanges); -static const int kLineTerminatorRanges[] = { 0x000A, 0x000B, 0x000D, 0x000E, - 0x2028, 0x202A, 0x10000 }; -static const int kLineTerminatorRangeCount = ArrayLength(kLineTerminatorRanges); static const int kMaxOneByteCharCode = 0xff; static const int kMaxUtf16CodeUnit = 0xffff; @@ -213,7 +162,7 @@ CharacterRange::AddClassEscapeUnicode(LifoAlloc* alloc, char16_t type, break; case 'w': if (ignore_case) - AddClass(kIgnoreCaseWordRanges, kIgnoreCaseWordCount, ranges); + AddClass(kIgnoreCaseWordRanges, kIgnoreCaseWordRangeCount, ranges); else AddClassEscape(alloc, type, ranges); break; @@ -233,33 +182,6 @@ CharacterRange::AddClassEscapeUnicode(LifoAlloc* alloc, char16_t type, } } -#define FOR_EACH_NON_ASCII_TO_ASCII_FOLDING(macro) \ - /* LATIN CAPITAL LETTER Y WITH DIAERESIS */ \ - macro(0x0178, 0x00FF) \ - /* LATIN SMALL LETTER LONG S */ \ - macro(0x017F, 0x0073) \ - /* LATIN CAPITAL LETTER SHARP S */ \ - macro(0x1E9E, 0x00DF) \ - /* KELVIN SIGN */ \ - macro(0x212A, 0x006B) \ - /* ANGSTROM SIGN */ \ - macro(0x212B, 0x00E5) - -// We need to check for the following characters: 0x39c 0x3bc 0x178. -static inline bool -RangeContainsLatin1Equivalents(CharacterRange range, bool unicode) -{ - /* TODO(dcarney): this could be a lot more efficient. */ - if (unicode) { -#define CHECK_RANGE(C, F) \ - if (range.Contains(C)) return true; -FOR_EACH_NON_ASCII_TO_ASCII_FOLDING(CHECK_RANGE) -#undef CHECK_RANGE - } - - return range.Contains(0x39c) || range.Contains(0x3bc) || range.Contains(0x178); -} - static bool RangesContainLatin1Equivalents(const CharacterRangeVector& ranges, bool unicode) { @@ -336,7 +258,7 @@ GetCaseIndependentLetters(char16_t character, // step 3.g. // The standard requires that non-ASCII characters cannot have ASCII // character codes in their equivalence class, even though this - // situation occurs multiple times in the unicode tables. + // situation occurs multiple times in the Unicode tables. static const unsigned kMaxAsciiCharCode = 127; if (upper <= kMaxAsciiCharCode) { if (character > kMaxAsciiCharCode) { @@ -365,31 +287,6 @@ GetCaseIndependentLetters(char16_t character, choices, ArrayLength(choices), letters); } -static char16_t -ConvertNonLatin1ToLatin1(char16_t c, bool unicode) -{ - MOZ_ASSERT(c > kMaxOneByteCharCode); - if (unicode) { - switch (c) { -#define CONVERT(C, F) case C: return F; -FOR_EACH_NON_ASCII_TO_ASCII_FOLDING(CONVERT) -#undef CONVERT - } - } - - switch (c) { - // This are equivalent characters in unicode. - case 0x39c: - case 0x3bc: - return 0xb5; - // This is an uppercase of a Latin-1 character - // outside of Latin-1. - case 0x178: - return 0xff; - } - return 0; -} - void CharacterRange::AddCaseEquivalents(bool is_ascii, bool unicode, CharacterRangeVector* ranges) { diff --git a/js/src/moz.build b/js/src/moz.build index 77acb10b9..a18170a75 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -196,6 +196,7 @@ UNIFIED_SOURCES += [ 'gc/Zone.cpp', 'irregexp/NativeRegExpMacroAssembler.cpp', 'irregexp/RegExpAST.cpp', + 'irregexp/RegExpCharacters.cpp', 'irregexp/RegExpEngine.cpp', 'irregexp/RegExpInterpreter.cpp', 'irregexp/RegExpMacroAssembler.cpp', diff --git a/js/src/vm/make_unicode.py b/js/src/vm/make_unicode.py index 5565d7d14..73c090ac9 100755 --- a/js/src/vm/make_unicode.py +++ b/js/src/vm/make_unicode.py @@ -133,6 +133,17 @@ def read_derived_core_properties(derived_core_properties): for char in range(int(start, 16), int(end, 16) + 1): yield (char, char_property) +def int_ranges(ints): + """ Yields consecutive ranges (inclusive) from integer values. """ + from itertools import tee, izip_longest + + (a, b) = tee(sorted(ints)) + start = next(b) + for (curr, succ) in izip_longest(a, b): + if curr + 1 != succ: + yield (start, curr) + start = succ + def utf16_encode(code): NonBMPMin = 0x10000 LeadSurrogateMin = 0xD800 @@ -740,6 +751,204 @@ def splitbins(t): assert t[i] == t2[(t1[i >> shift] << shift) + (i & mask)] return best +def make_irregexp_tables(version, + table, index, + folding_table, folding_index, + test_table): + import string + from functools import partial + from itertools import chain, ifilter, imap + + MAX_ASCII = 0x7F + MAX_LATIN1 = 0xFF + LEAD_SURROGATE_MIN = 0xD800 + TRAIL_SURROGATE_MAX = 0xDFFF + + def hex2(n): + assert 0 <= n and n < 16**2 + return '0x{:02X}'.format(n) + + def hex4(n): + assert 0 <= n and n < 16**4 + return '0x{:04X}'.format(n) + + def uhex4(n): + assert 0 <= n and n < 16**4 + return 'U+{:04X}'.format(n) + + def case_info(code): + assert 0 <= code and code <= MAX_BMP + (upper, lower, flags) = table[index[code]] + return ((code + upper) & 0xffff, (code + lower) & 0xffff, flags) + + def is_space(code): + (_, _, flags) = case_info(code) + return bool(flags & FLAG_SPACE) + + def to_upper(code): + (upper, _, _) = case_info(code) + return upper + + def casefold(code): + assert 0 <= code and code <= MAX_BMP + (folding, _, _, _) = folding_table[folding_index[code]] + return (code + folding) & 0xffff + + def casefolds_to_ascii(code): + return casefold(code) <= MAX_ASCII + + def casefolds_to_latin1(code): + return casefold(code) <= MAX_LATIN1 + + def casemaps_to_nonlatin1(code): + upper = to_upper(code) + return upper > MAX_LATIN1 + + def char_name(code): + assert 0 <= code and code <= MAX_BMP + if code not in test_table: + return '' + if code == LEAD_SURROGATE_MIN: + return '' + if code == TRAIL_SURROGATE_MAX: + return '' + (_, _, name, alias) = test_table[code] + return name if not name.startswith('<') else alias + + def write_character_range(println, name, characters): + char_ranges = list(int_ranges(characters)) + println('') + println('const int js::irregexp::k{}Ranges[] = {{'.format(name)) + for (start, end) in char_ranges: + s_name = char_name(start) + e_name = char_name(end) + println(' {}, {} + 1, // {}'.format(hex4(start), hex4(end), + '{}..{}'.format(s_name, e_name) + if start != end else s_name)) + println(' {} + 1'.format(hex4(MAX_BMP))) + println('};') + println('const int js::irregexp::k{}RangeCount = {};'.format(name, + len(char_ranges) * 2 + 1)) + + def write_character_test(println, test, consequent, default): + # Latin1 characters which, when case-mapped through + # String.prototype.toUpperCase(), canonicalize to a non-Latin1 character. + # ES2017, §21.2.2.8.2 Runtime Semantics: Canonicalize + casemapped_to_nonlatin1 = ifilter(casemaps_to_nonlatin1, xrange(0, MAX_LATIN1 + 1)) + + def casemap_closure(ch): + upper = to_upper(ch) + return (ch, [c for c in xrange(MAX_LATIN1 + 1, MAX_BMP + 1) if upper == to_upper(c)]) + + # Mapping from Latin1 characters to the list of case map equivalent + # non-Latin1 characters. + casemap_for_latin1 = dict(chain(imap(casemap_closure, casemapped_to_nonlatin1))) + + # Non-latin1 characters which, when Unicode case-folded, canonicalize to + # a Latin1 character. + # ES2017, §21.2.2.8.2 Runtime Semantics: Canonicalize + casefolded_to_latin1 = ifilter(casefolds_to_latin1, xrange(MAX_LATIN1 + 1, MAX_BMP + 1)) + + println(' if (unicode) {') + for ch in casefolded_to_latin1: + casefolded = casefold(ch) + # Skip if also handled below for case mapping. + if casefolded in casemap_for_latin1 and ch in casemap_for_latin1[casefolded]: + continue + println(' // "{}" case folds to "{}".'.format(char_name(ch), + char_name(casefolded))) + println(' if ({})'.format(test(ch))) + println(' return {};'.format(consequent(casefolded))) + println(' }') + println('') + for (ch, casemapped_chars) in casemap_for_latin1.iteritems(): + for casemapped in casemapped_chars: + println(' // "{}" case maps to "{}".'.format(char_name(casemapped), + char_name(ch))) + println(' if ({})'.format(' || '.join(imap(test, casemapped_chars)))) + println(' return {};'.format(consequent(ch))) + println(' return {};'.format(default)) + + with io.open('../irregexp/RegExpCharacters-inl.h', 'wb') as chars_file: + write = partial(print, file=chars_file, sep='', end='') + println = partial(write, end='\n') + + write(warning_message) + write(unicode_version_message.format(version)) + + println('#ifndef V8_JSREGEXPCHARACTERS_INL_H_') + println('#define V8_JSREGEXPCHARACTERS_INL_H_') + println('') + println('namespace js {') + println('') + println('namespace irregexp {') + println('') + + println('static inline bool') + println('RangeContainsLatin1Equivalents(CharacterRange range, bool unicode)') + println('{') + write_character_test(println, lambda ch: 'range.Contains({})'.format(hex4(ch)), + lambda _: 'true', 'false') + println('}') + + println('') + println('} } // namespace js::irregexp') + println('') + println('#endif // V8_JSREGEXPCHARACTERS_INL_H_') + + with io.open('../irregexp/RegExpCharacters.cpp', 'wb') as chars_file: + write = partial(print, file=chars_file, sep='', end='') + println = partial(write, end='\n') + character_range = partial(write_character_range, println) + + # Characters in \s, 21.2.2.12 CharacterClassEscape. + space_chars = filter(is_space, xrange(0, MAX_BMP + 1)) + + # Characters in \d, 21.2.2.12 CharacterClassEscape. + digit_chars = map(ord, string.digits) + assert all(ch <= MAX_ASCII for ch in digit_chars) + + # Characters in \w, 21.2.2.12 CharacterClassEscape. + word_chars = map(ord, string.digits + string.ascii_letters + '_') + assert all(ch <= MAX_ASCII for ch in word_chars) + + # Characters which case-fold to characters in \w. + ignorecase_word_chars = (word_chars + + filter(casefolds_to_ascii, xrange(MAX_ASCII + 1, MAX_BMP + 1))) + + # Surrogate characters. + surrogate_chars = range(LEAD_SURROGATE_MIN, TRAIL_SURROGATE_MAX + 1) + + write(warning_message) + write(unicode_version_message.format(version)) + println('#include "irregexp/RegExpCharacters.h"') + println('') + println('#include "mozilla/Assertions.h"') + println('') + + println('char16_t') + println('js::irregexp::ConvertNonLatin1ToLatin1(char16_t c, bool unicode)') + println('{') + println(' MOZ_ASSERT(c > {}, "Character mustn\'t be Latin1");'.format(hex2(MAX_LATIN1))) + write_character_test(println, lambda ch: 'c == {}'.format(hex4(ch)), hex2, '0') + println('}') + + character_range('Space', space_chars) + character_range('SpaceAndSurrogate', space_chars + surrogate_chars) + + character_range('Word', word_chars) + character_range('IgnoreCaseWord', ignorecase_word_chars) + character_range('WordAndSurrogate', word_chars + surrogate_chars) + character_range('NegatedIgnoreCaseWordAndSurrogate', + set(xrange(0, MAX_BMP + 1)) - set(ignorecase_word_chars + surrogate_chars)) + + character_range('Digit', digit_chars) + character_range('DigitAndSurrogate', digit_chars + surrogate_chars) + + character_range('Surrogate', surrogate_chars) + + character_range('LineTerminator', line_terminator) + def update_unicode(args): import urllib2 @@ -807,6 +1016,10 @@ def update_unicode(args): make_non_bmp_file(unicode_version, non_bmp_lower_map, non_bmp_upper_map, non_bmp_folding_map, non_bmp_rev_folding_map) + make_irregexp_tables(unicode_version, + table, index, + folding_table, folding_index, + test_table) make_bmp_mapping_test(unicode_version, test_table) make_non_bmp_mapping_test(unicode_version, non_bmp_upper_map, non_bmp_lower_map) -- cgit v1.2.3 From 70a4d7e010785cf1857510aed711282b25f0c659 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 17 Aug 2017 20:37:46 +0200 Subject: JS - RegExp - match updated spec for `/\b/iu` and `/\B/iu` --- js/src/irregexp/RegExpEngine.cpp | 25 ++++++++++++++++------ js/src/irregexp/RegExpEngine.h | 8 +++++-- .../RegExp/unicode-ignoreCase-word-boundary.js | 25 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js (limited to 'js/src') diff --git a/js/src/irregexp/RegExpEngine.cpp b/js/src/irregexp/RegExpEngine.cpp index 0011e976f..4d691a5dc 100644 --- a/js/src/irregexp/RegExpEngine.cpp +++ b/js/src/irregexp/RegExpEngine.cpp @@ -2255,7 +2255,10 @@ void BoyerMoorePositionInfo::SetInterval(const Interval& interval) { s_ = AddRange(s_, kSpaceRanges, kSpaceRangeCount, interval); - w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); + if (unicode_ignore_case_) + w_ = AddRange(w_, kIgnoreCaseWordRanges, kIgnoreCaseWordRangeCount, interval); + else + w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); d_ = AddRange(d_, kDigitRanges, kDigitRangeCount, interval); surrogate_ = AddRange(surrogate_, kSurrogateRanges, kSurrogateRangeCount, interval); @@ -2292,11 +2295,12 @@ BoyerMoorePositionInfo::SetAll() BoyerMooreLookahead::BoyerMooreLookahead(LifoAlloc* alloc, size_t length, RegExpCompiler* compiler) : length_(length), compiler_(compiler), bitmaps_(*alloc) { + bool unicode_ignore_case = compiler->unicode() && compiler->ignore_case(); max_char_ = MaximumCharacter(compiler->ascii()); bitmaps_.reserve(length); for (size_t i = 0; i < length; i++) - bitmaps_.append(alloc->newInfallible(alloc)); + bitmaps_.append(alloc->newInfallible(alloc, unicode_ignore_case)); } // Find the longest range of lookahead that has the fewest number of different @@ -2962,15 +2966,22 @@ EmitNotInSurrogatePair(RegExpCompiler* compiler, RegExpNode* on_success, Trace* // Check for [0-9A-Z_a-z]. static void EmitWordCheck(RegExpMacroAssembler* assembler, - jit::Label* word, jit::Label* non_word, bool fall_through_on_word) + jit::Label* word, jit::Label* non_word, bool fall_through_on_word, + bool unicode_ignore_case) { - if (assembler->CheckSpecialCharacterClass(fall_through_on_word ? 'w' : 'W', + if (!unicode_ignore_case && + assembler->CheckSpecialCharacterClass(fall_through_on_word ? 'w' : 'W', fall_through_on_word ? non_word : word)) { // Optimized implementation available. return; } + if (unicode_ignore_case) { + assembler->CheckCharacter(0x017F, word); + assembler->CheckCharacter(0x212A, word); + } + assembler->CheckCharacterGT('z', non_word); assembler->CheckCharacterLT('0', non_word); assembler->CheckCharacterGT('a' - 1, word); @@ -3019,7 +3030,8 @@ AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace) assembler->LoadCurrentCharacter(trace->cp_offset(), &before_non_word); } // Fall through on non-word. - EmitWordCheck(assembler, &before_word, &before_non_word, false); + EmitWordCheck(assembler, &before_word, &before_non_word, false, + compiler->unicode() && compiler->ignore_case()); // Next character is not a word character. assembler->Bind(&before_non_word); jit::Label ok; @@ -3059,7 +3071,8 @@ AssertionNode::BacktrackIfPrevious(RegExpCompiler* compiler, // We already checked that we are not at the start of input so it must be // OK to load the previous character. assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, &dummy, false); - EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord); + EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord, + compiler->unicode() && compiler->ignore_case()); assembler->Bind(&fall_through); on_success()->Emit(compiler, &new_trace); diff --git a/js/src/irregexp/RegExpEngine.h b/js/src/irregexp/RegExpEngine.h index 78c784aaf..1a8fd4b22 100644 --- a/js/src/irregexp/RegExpEngine.h +++ b/js/src/irregexp/RegExpEngine.h @@ -1195,13 +1195,14 @@ AddRange(ContainedInLattice a, class BoyerMoorePositionInfo { public: - explicit BoyerMoorePositionInfo(LifoAlloc* alloc) + explicit BoyerMoorePositionInfo(LifoAlloc* alloc, bool unicode_ignore_case) : map_(*alloc), map_count_(0), w_(kNotYet), s_(kNotYet), d_(kNotYet), - surrogate_(kNotYet) + surrogate_(kNotYet), + unicode_ignore_case_(unicode_ignore_case) { map_.reserve(kMapSize); for (int i = 0; i < kMapSize; i++) @@ -1228,6 +1229,9 @@ class BoyerMoorePositionInfo ContainedInLattice s_; // The \s character class. ContainedInLattice d_; // The \d character class. ContainedInLattice surrogate_; // Surrogate UTF-16 code units. + + // True if the RegExp has unicode and ignoreCase flags. + bool unicode_ignore_case_; }; typedef InfallibleVector BoyerMoorePositionInfoVector; diff --git a/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js b/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js new file mode 100644 index 000000000..c1a04bd3d --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/unicode-ignoreCase-word-boundary.js @@ -0,0 +1,25 @@ +var BUGNUMBER = 1338373; +var summary = "Word boundary should match U+017F and U+212A in unicode+ignoreCase."; + +assertEq(/\b/iu.test('\u017F'), true); +assertEq(/\b/i.test('\u017F'), false); +assertEq(/\b/u.test('\u017F'), false); +assertEq(/\b/.test('\u017F'), false); + +assertEq(/\b/iu.test('\u212A'), true); +assertEq(/\b/i.test('\u212A'), false); +assertEq(/\b/u.test('\u212A'), false); +assertEq(/\b/.test('\u212A'), false); + +assertEq(/\B/iu.test('\u017F'), false); +assertEq(/\B/i.test('\u017F'), true); +assertEq(/\B/u.test('\u017F'), true); +assertEq(/\B/.test('\u017F'), true); + +assertEq(/\B/iu.test('\u212A'), false); +assertEq(/\B/i.test('\u212A'), true); +assertEq(/\B/u.test('\u212A'), true); +assertEq(/\B/.test('\u212A'), true); + +if (typeof reportCompare === "function") + reportCompare(true, true); -- cgit v1.2.3 From d9ef671b04f43a30f3fcd2db4e351a3cbd811f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 1 Feb 2018 05:44:11 -0800 Subject: Bug 1430761 - Update tzdata in ICU data files to 2018c. r=Waldo, a=lizzard --HG-- extra : rebase_source : cb9ac8a678b6f565091f6d7733b6cd86afde0da7 --- js/src/builtin/IntlTimeZoneData.h | 2 +- js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js | 2 +- js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js | 3 +-- js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js | 2 +- js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/builtin/IntlTimeZoneData.h b/js/src/builtin/IntlTimeZoneData.h index bb7d22109..f92c583df 100644 --- a/js/src/builtin/IntlTimeZoneData.h +++ b/js/src/builtin/IntlTimeZoneData.h @@ -1,5 +1,5 @@ // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2017c +// tzdata version = 2018c #ifndef builtin_IntlTimeZoneData_h #define builtin_IntlTimeZoneData_h diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js index aeb1fc043..890b1c1d5 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2017c +// tzdata version = 2018c const tzMapper = [ x => x, diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js index b9d957a82..19fd871eb 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2017c +// tzdata version = 2018c const tzMapper = [ x => x, @@ -47,7 +47,6 @@ const links = { "Africa/Nouakchott": "Africa/Nouakchott", "Africa/Ouagadougou": "Africa/Ouagadougou", "Africa/Porto-Novo": "Africa/Porto-Novo", - "Africa/Sao_Tome": "Africa/Sao_Tome", "Africa/Timbuktu": "Africa/Timbuktu", "America/Anguilla": "America/Anguilla", "America/Antigua": "America/Antigua", diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js index 7a358e997..34425acec 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2017c +// tzdata version = 2018c const tzMapper = [ x => x, diff --git a/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js index 9515cb394..8b2dedec2 100644 --- a/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js +++ b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js @@ -1,7 +1,7 @@ // |reftest| skip-if(!this.hasOwnProperty("Intl")) // Generated by make_intl_data.py. DO NOT EDIT. -// tzdata version = 2017c +// tzdata version = 2018c const tzMapper = [ x => x, -- cgit v1.2.3 From 69d5d1e702024e584536c88a5d604889c5bcd14d Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Fri, 23 Feb 2018 13:25:53 -0500 Subject: Bug 1437507 - Fix JSObject::setFlags to call ensureShape before checking for dictionary mode. r=jandem, a=RyanVM --HG-- extra : source : ca6b74831ec3db204e024b07f200b0d1ce93557e extra : intermediate-source : 9d7c295d9570e294851908465f56ec0779547d2a --- js/src/vm/Shape.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index a64dc529a..306a2c540 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -1214,6 +1214,10 @@ JSObject::setFlags(ExclusiveContext* cx, BaseShape::Flag flags, GenerateShape ge RootedObject self(cx, this); + Shape* existingShape = self->ensureShape(cx); + if (!existingShape) + return false; + if (isNative() && as().inDictionaryMode()) { if (generateShape == GENERATE_SHAPE && !as().generateOwnShape(cx)) return false; @@ -1227,10 +1231,6 @@ JSObject::setFlags(ExclusiveContext* cx, BaseShape::Flag flags, GenerateShape ge return true; } - Shape* existingShape = self->ensureShape(cx); - if (!existingShape) - return false; - Shape* newShape = Shape::setObjectFlags(cx, flags, self->taggedProto(), existingShape); if (!newShape) return false; -- cgit v1.2.3 From a32b7f7c4e4e31669e0787e6321d74e4db71e514 Mon Sep 17 00:00:00 2001 From: Tom Ritter Date: Tue, 20 Feb 2018 12:18:30 -0600 Subject: Bug 1430173 - Reduce the precision of all explicit clocks to 2ms. r=baku, a=RyanVM Backport to ESR where we don't have the ResistFingerprinting component. MozReview-Commit-ID: 9bjycHjR3SF --HG-- extra : transplant_source : %EA%03%21%0A%E9%3F%8E%CD%7C%D79f%96%85%96%00%5D%7F%95X --- js/src/jsdate.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) mode change 100644 => 100755 js/src/jsdate.cpp (limited to 'js/src') diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp old mode 100644 new mode 100755 index d73fb93d2..ccaeda2a3 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -1232,7 +1232,10 @@ date_parse(JSContext* cx, unsigned argc, Value* vp) static ClippedTime NowAsMillis() { - return TimeClip(static_cast(PRMJ_Now()) / PRMJ_USEC_PER_MSEC); + const double maxResolutionMs = 2; + double timestamp = static_cast(PRMJ_Now()) / PRMJ_USEC_PER_MSEC; + timestamp = floor(timestamp / maxResolutionMs) * maxResolutionMs; + return TimeClip(timestamp); } bool -- cgit v1.2.3 From 64d560f52bed0065fb7267ff87ad435eacef90fd Mon Sep 17 00:00:00 2001 From: Jan de Mooij Date: Thu, 1 Mar 2018 15:36:13 +0100 Subject: Bug 1437450 - Disable Ion no-clone optimization for regexps if the graph contains try blocks. r=nbp, a=RyanVM --HG-- extra : source : 61b461277369e0cec89c79d8526a82c575818e94 extra : intermediate-source : 806696d494300c8c09ad2c0c3141194b5418a706 extra : histedit_source : e38bbe5c22bace744f6da0b8ff39462b64fe5a95 --- js/src/jit/IonAnalysis.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'js/src') diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 90303255d..2c9ffb607 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -2188,6 +2188,12 @@ IsRegExpHoistable(MIRGenerator* mir, MDefinition* regexp, MDefinitionVector& wor bool jit::MakeMRegExpHoistable(MIRGenerator* mir, MIRGraph& graph) { + // If we are compiling try blocks, regular expressions may be observable + // from catch blocks (which Ion does not compile). For now just disable the + // pass in this case. + if (graph.hasTryBlock()) + return true; + MDefinitionVector worklist(graph.alloc()); for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { -- cgit v1.2.3 From c9e08a8af9cc2b9597d75eef1192595252a29550 Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Fri, 5 Jan 2018 14:37:47 -0800 Subject: Bug 1434384 - Mark v1 structured clone data as cross-process. r=jorendorff, a=RyanVM --HG-- extra : source : d85679eb427513cb18650f3d4e7d37a6ccbefbab extra : intermediate-source : 5c286cc709dfcaca7269b88516e6f71626c98496 --- js/src/vm/StructuredClone.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index 4b01cda85..3a062c3b8 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -2188,12 +2188,14 @@ JSStructuredCloneReader::readHeader() return in.reportTruncated(); if (tag != SCTAG_HEADER) { - // Old structured clone buffer. We must have read it from disk or - // somewhere, so we can assume it's scope-compatible. + // Old structured clone buffer. We must have read it from disk. + storedScope = JS::StructuredCloneScope::DifferentProcess; return true; } MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); + storedScope = JS::StructuredCloneScope(data); + if (data != uint32_t(JS::StructuredCloneScope::SameProcessSameThread) && data != uint32_t(JS::StructuredCloneScope::SameProcessDifferentThread) && data != uint32_t(JS::StructuredCloneScope::DifferentProcess)) @@ -2202,7 +2204,6 @@ JSStructuredCloneReader::readHeader() "invalid structured clone scope"); return false; } - storedScope = JS::StructuredCloneScope(data); if (storedScope < allowedScope) { JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, "incompatible structured clone scope"); -- cgit v1.2.3 From caa1b329f11d39515e29ad93fb1ff72595fce8d4 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Wed, 14 Mar 2018 16:58:35 -0400 Subject: Fix mozbuild looking for .cargo --- js/src/make-source-package.sh | 4 ---- 1 file changed, 4 deletions(-) (limited to 'js/src') diff --git a/js/src/make-source-package.sh b/js/src/make-source-package.sh index f4131b98f..6e44dd977 100755 --- a/js/src/make-source-package.sh +++ b/js/src/make-source-package.sh @@ -95,10 +95,6 @@ case $cmd in # copy build and config directory. cp -pPR ${TOPSRCDIR}/build ${TOPSRCDIR}/config ${tgtpath} - # copy cargo config - ${MKDIR} -p ${tgtpath}/.cargo - cp -pPR ${TOPSRCDIR}/.cargo/config.in ${tgtpath}/.cargo - # generate configure files to avoid build dependency on autoconf-2.13 cp -PR ${TOPSRCDIR}/js/src/configure.in ${tgtpath}/js/src/configure chmod a+x ${tgtpath}/js/src/configure -- cgit v1.2.3 From 75c9377766326589faa844a95d5997a156f6aed0 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 15 Mar 2018 21:11:35 +0100 Subject: Close iterator after error in {Map,Set,WeakMap,WeakSet} constructors Issue #17 --- js/src/builtin/Map.js | 11 +- js/src/builtin/Set.js | 7 +- js/src/builtin/Utilities.js | 23 ++ js/src/builtin/WeakMap.js | 11 +- js/src/builtin/WeakSet.js | 7 +- .../tests/ecma_6/Map/constructor-iterator-close.js | 253 +++++++++++++++++++++ 6 files changed, 306 insertions(+), 6 deletions(-) create mode 100644 js/src/tests/ecma_6/Map/constructor-iterator-close.js (limited to 'js/src') diff --git a/js/src/builtin/Map.js b/js/src/builtin/Map.js index 432364614..27a12bfff 100644 --- a/js/src/builtin/Map.js +++ b/js/src/builtin/Map.js @@ -40,11 +40,18 @@ function MapConstructorInit(iterable) { var nextItem = next.value; // Step 8.d. - if (!IsObject(nextItem)) + if (!IsObject(nextItem)) { + IteratorCloseThrow(iter); ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Map"); + } // Steps 8.e-j. - callContentFunction(adder, map, nextItem[0], nextItem[1]); + try { + callContentFunction(adder, map, nextItem[0], nextItem[1]); + } catch (e) { + IteratorCloseThrow(iter); + throw e; + } } } diff --git a/js/src/builtin/Set.js b/js/src/builtin/Set.js index accc70120..c61a49ef8 100644 --- a/js/src/builtin/Set.js +++ b/js/src/builtin/Set.js @@ -40,7 +40,12 @@ function SetConstructorInit(iterable) { var nextValue = next.value; // Steps 8.d-e. - callContentFunction(adder, set, nextValue); + try { + callContentFunction(adder, set, nextValue); + } catch (e) { + IteratorCloseThrow(iter); + throw e; + } } } diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index bfb1fe7f4..2dece3801 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -154,6 +154,29 @@ function GetIterator(obj, method) { return iterator; } +// ES2017 draft rev 7.4.6. +// When completion.[[Type]] is throw. +function IteratorCloseThrow(iter) { + // Steps 1-2 (implicit) + + // Step 3. + var returnMethod = GetMethod(iter, "return"); + + // Step 4 (done in caller). + if (returnMethod === undefined) + return; + + try { + // Step 5. + callContentFunction(returnMethod, iter); + } catch (e) { + } + + // Step 6 (done in caller). + + // Steps 7-9 (skipped). +} + var _builtinCtorsCache = {__proto__: null}; function GetBuiltinConstructor(builtinName) { diff --git a/js/src/builtin/WeakMap.js b/js/src/builtin/WeakMap.js index 066a72bfe..708be8424 100644 --- a/js/src/builtin/WeakMap.js +++ b/js/src/builtin/WeakMap.js @@ -40,10 +40,17 @@ function WeakMapConstructorInit(iterable) { var nextItem = next.value; // Step 8.d. - if (!IsObject(nextItem)) + if (!IsObject(nextItem)) { + IteratorCloseThrow(iter); ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "WeakMap"); + } // Steps 8.e-j. - callContentFunction(adder, map, nextItem[0], nextItem[1]); + try { + callContentFunction(adder, map, nextItem[0], nextItem[1]); + } catch (e) { + IteratorCloseThrow(iter); + throw e; + } } } diff --git a/js/src/builtin/WeakSet.js b/js/src/builtin/WeakSet.js index eb7c2378f..8589f9dc6 100644 --- a/js/src/builtin/WeakSet.js +++ b/js/src/builtin/WeakSet.js @@ -40,7 +40,12 @@ function WeakSetConstructorInit(iterable) { var nextValue = next.value; // Steps 8.d-e. - callContentFunction(adder, set, nextValue); + try { + callContentFunction(adder, set, nextValue); + } catch (e) { + IteratorCloseThrow(iter); + throw e; + } } } diff --git a/js/src/tests/ecma_6/Map/constructor-iterator-close.js b/js/src/tests/ecma_6/Map/constructor-iterator-close.js new file mode 100644 index 000000000..ba4bee4a7 --- /dev/null +++ b/js/src/tests/ecma_6/Map/constructor-iterator-close.js @@ -0,0 +1,253 @@ +var BUGNUMBER = 1180306; +var summary = 'Map/Set/WeakMap/WeakSet constructor should close iterator on error'; + +print(BUGNUMBER + ": " + summary); + +function test(ctors, { nextVal=undefined, + nextThrowVal=undefined, + modifier=undefined, + exceptionVal=undefined, + exceptionType=undefined, + closed=true }) { + function getIterable() { + let iterable = { + closed: false, + [Symbol.iterator]() { + let iterator = { + first: true, + next() { + if (this.first) { + this.first = false; + if (nextThrowVal) + throw nextThrowVal; + return nextVal; + } + return { value: undefined, done: true }; + }, + return() { + iterable.closed = true; + return {}; + } + }; + if (modifier) + modifier(iterator, iterable); + + return iterator; + } + }; + return iterable; + } + + for (let ctor of ctors) { + let iterable = getIterable(); + if (exceptionVal) { + let caught = false; + try { + new ctor(iterable); + } catch (e) { + assertEq(e, exceptionVal); + caught = true; + } + assertEq(caught, true); + } else if (exceptionType) { + assertThrowsInstanceOf(() => new ctor(iterable), exceptionType); + } else { + new ctor(iterable); + } + assertEq(iterable.closed, closed); + } +} + +// == Error cases with close == + +// ES 2017 draft 23.1.1.1 Map step 8.d.ii. +// ES 2017 draft 23.3.1.1 WeakMap step 8.d.ii. +test([Map, WeakMap], { + nextVal: { value: "non object", done: false }, + exceptionType: TypeError, + closed: true, +}); + +// ES 2017 draft 23.1.1.1 Map step 8.f. +// ES 2017 draft 23.3.1.1 WeakMap step 8.f. +test([Map, WeakMap], { + nextVal: { value: { get 0() { throw "0 getter throws"; } }, done: false }, + exceptionVal: "0 getter throws", + closed: true, +}); + +// ES 2017 draft 23.1.1.1 Map step 8.h. +// ES 2017 draft 23.3.1.1 WeakMap step 8.h. +test([Map, WeakMap], { + nextVal: { value: { 0: {}, get 1() { throw "1 getter throws"; } }, done: false }, + exceptionVal: "1 getter throws", + closed: true, +}); + +// ES 2017 draft 23.1.1.1 Map step 8.j. +// ES 2017 draft 23.3.1.1 WeakMap step 8.j. +class MyMap extends Map { + set(k, v) { + throw "setter throws"; + } +} +class MyWeakMap extends WeakMap { + set(k, v) { + throw "setter throws"; + } +} +test([MyMap, MyWeakMap], { + nextVal: { value: [{}, {}], done: false }, + exceptionVal: "setter throws", + closed: true, +}); + +// ES 2017 draft 23.2.1.1 Set step 8.e. +// ES 2017 draft 23.4.1.1 WeakSet step 8.e. +class MySet extends Set { + add(v) { + throw "adder throws"; + } +} +class MyWeakSet extends WeakSet { + add(v) { + throw "adder throws"; + } +} +test([MySet, MyWeakSet], { + nextVal: { value: {}, done: false }, + exceptionVal: "adder throws", + closed: true, +}); + +// ES 2017 draft 7.4.6 step 3. +// if GetMethod fails, the thrown value should be used. +test([MyMap, MySet, MyWeakMap, MyWeakSet], { + nextVal: { value: [{}, {}], done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + throw "return getter throws"; + } + }); + }, + exceptionVal: "return getter throws", + closed: true, +}); +test([MyMap, MySet, MyWeakMap, MyWeakSet], { + nextVal: { value: [{}, {}], done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + return "non object"; + } + }); + }, + exceptionType: TypeError, + closed: true, +}); +test([MyMap, MySet, MyWeakMap, MyWeakSet], { + nextVal: { value: [{}, {}], done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + // Non callable. + return {}; + } + }); + }, + exceptionType: TypeError, + closed: true, +}); + +// ES 2017 draft 7.4.6 steps 6. +// if return method throws, the thrown value should be ignored. +test([MyMap, MyWeakMap], { + nextVal: { value: [{}, {}], done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + throw "return throws"; + }; + }, + exceptionVal: "setter throws", + closed: true, +}); +test([MySet, MyWeakSet], { + nextVal: { value: [{}, {}], done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + throw "return throws"; + }; + }, + exceptionVal: "adder throws", + closed: true, +}); + +test([MyMap, MyWeakMap], { + nextVal: { value: [{}, {}], done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + return "non object"; + }; + }, + exceptionVal: "setter throws", + closed: true, +}); +test([MySet, MyWeakSet], { + nextVal: { value: [{}, {}], done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + return "non object"; + }; + }, + exceptionVal: "adder throws", + closed: true, +}); + +// == Error cases without close == + +// ES 2017 draft 23.1.1.1 Map step 8.a. +// ES 2017 draft 23.3.1.1 WeakMap step 8.a. +// ES 2017 draft 23.2.1.1 Set step 8.a. +// ES 2017 draft 23.4.1.1 WeakSet step 8.a. +test([Map, WeakMap, Set, WeakSet], { + nextThrowVal: "next throws", + exceptionVal: "next throws", + closed: false, +}); +test([Map, WeakMap, Set, WeakSet], { + nextVal: { value: {}, get done() { throw "done getter throws"; } }, + exceptionVal: "done getter throws", + closed: false, +}); + +// ES 2017 draft 23.1.1.1 Map step 8.c. +// ES 2017 draft 23.3.1.1 WeakMap step 8.c. +// ES 2017 draft 23.2.1.1 Set step 8.c. +// ES 2017 draft 23.4.1.1 WeakSet step 8.c. +test([Map, WeakMap, Set, WeakSet], { + nextVal: { get value() { throw "value getter throws"; }, done: false }, + exceptionVal: "value getter throws", + closed: false, +}); + +// == Successful cases == + +test([Map, WeakMap], { + nextVal: { value: [{}, {}], done: false }, + closed: false, +}); +test([Set, WeakSet], { + nextVal: { value: {}, done: false }, + closed: false, +}); + +if (typeof reportCompare === 'function') + reportCompare(true, true); -- cgit v1.2.3 From 25550ce903d01f31bead59de945e4adf86819440 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 15 Mar 2018 21:12:03 +0100 Subject: Close iterator after error in Array.from Issue #17 --- js/src/builtin/Array.js | 46 ++++-- js/src/tests/ecma_6/Array/from-iterator-close.js | 183 +++++++++++++++++++++++ 2 files changed, 215 insertions(+), 14 deletions(-) create mode 100644 js/src/tests/ecma_6/Array/from-iterator-close.js (limited to 'js/src') diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 54b47b72f..5ab0b71be 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -784,7 +784,7 @@ function ArrayKeys() { return CreateArrayIterator(this, ITEM_KIND_KEY); } -// ES6 draft rev31 (2015/01/15) 22.1.2.1 Array.from(source[, mapfn[, thisArg]]). +// ES 2017 draft 0f10dba4ad18de92d47d421f378233a2eae8f077 22.1.2.1 function ArrayFrom(items, mapfn=undefined, thisArg=undefined) { // Step 1. var C = this; @@ -795,43 +795,61 @@ function ArrayFrom(items, mapfn=undefined, thisArg=undefined) { ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, mapfn)); var T = thisArg; - // Steps 4-5. + // Step 4. var usingIterator = GetMethod(items, std_iterator); - // Step 6. + // Step 5. if (usingIterator !== undefined) { - // Steps 6.a-c. + // Steps 5.a-c. var A = IsConstructor(C) ? new C() : []; - // Steps 6.d-e. + // Step 5.c. var iterator = GetIterator(items, usingIterator); - // Step 6.f. + // Step 5.d. var k = 0; - // Step 6.g. + // Step 5.e. // These steps cannot be implemented using a for-of loop. // See . while (true) { - // Steps 6.g.i-iii. + // Step 5.e.i. + // Disabled for performance reason. We won't hit this case on + // normal array, since _DefineDataProperty will throw before it. + // We could hit this when |A| is a proxy and it ignores + // |_DefineDataProperty|, but it happens only after too long loop. + /* + if (k >= 0x1fffffffffffff) { + IteratorCloseThrow(iterator); + ThrowTypeError(JSMSG_TOO_LONG_ARRAY); + } + */ + + // Step 5.e.iii. var next = callContentFunction(iterator.next, iterator); if (!IsObject(next)) ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); - // Step 6.g.iv. + // Step 5.e.iv. if (next.done) { A.length = k; return A; } - // Steps 6.g.v-vi. + // Steps 5.e.v. var nextValue = next.value; - // Steps 6.g.vii-viii. - var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue; + // Steps 5.e.vi-vii. + try { + var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue; - // Steps 6.g.ix-xi. - _DefineDataProperty(A, k++, mappedValue); + // Steps 5.e.ii (reordered), 5.e.viii. + _DefineDataProperty(A, k++, mappedValue); + } catch (e) { + // Steps 5.e.vi.2, 5.e.ix. + IteratorCloseThrow(iterator); + throw e; + } } } diff --git a/js/src/tests/ecma_6/Array/from-iterator-close.js b/js/src/tests/ecma_6/Array/from-iterator-close.js new file mode 100644 index 000000000..fa97ea282 --- /dev/null +++ b/js/src/tests/ecma_6/Array/from-iterator-close.js @@ -0,0 +1,183 @@ +var BUGNUMBER = 1180306; +var summary = 'Array.from should close iterator on error'; + +print(BUGNUMBER + ": " + summary); + +function test(ctor, { mapVal=undefined, + nextVal=undefined, + nextThrowVal=undefined, + modifier=undefined, + exceptionVal=undefined, + exceptionType=undefined, + closed=true }) { + let iterable = { + closed: false, + [Symbol.iterator]() { + let iterator = { + first: true, + next() { + if (this.first) { + this.first = false; + if (nextThrowVal) + throw nextThrowVal; + return nextVal; + } + return { value: undefined, done: true }; + }, + return() { + iterable.closed = true; + return {}; + } + }; + if (modifier) + modifier(iterator, iterable); + + return iterator; + } + }; + if (exceptionVal) { + let caught = false; + try { + ctor.from(iterable, mapVal); + } catch (e) { + assertEq(e, exceptionVal); + caught = true; + } + assertEq(caught, true); + } else if (exceptionType) { + assertThrowsInstanceOf(() => ctor.from(iterable, mapVal), exceptionType); + } else { + ctor.from(iterable, mapVal); + } + assertEq(iterable.closed, closed); +} + +// == Error cases with close == + +// ES 2017 draft 22.1.2.1 step 5.e.i.1. +// Cannot test. + +// ES 2017 draft 22.1.2.1 step 5.e.vi.2. +test(Array, { + mapVal: () => { throw "map throws"; }, + nextVal: { value: 1, done: false }, + exceptionVal: "map throws", + closed: true, +}); + +// ES 2017 draft 22.1.2.1 step 5.e.ix. +class MyArray extends Array { + constructor() { + return new Proxy({}, { + defineProperty() { + throw "defineProperty throws"; + } + }); + } +} +test(MyArray, { + nextVal: { value: 1, done: false }, + exceptionVal: "defineProperty throws", + closed: true, +}); + +// ES 2017 draft 7.4.6 step 3. +// if GetMethod fails, the thrown value should be used. +test(MyArray, { + nextVal: { value: 1, done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + throw "return getter throws"; + } + }); + }, + exceptionVal: "return getter throws", + closed: true, +}); +test(MyArray, { + nextVal: { value: 1, done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + return "non object"; + } + }); + }, + exceptionType: TypeError, + closed: true, +}); +test(MyArray, { + nextVal: { value: 1, done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + // Non callable. + return {}; + } + }); + }, + exceptionType: TypeError, + closed: true, +}); + +// ES 2017 draft 7.4.6 steps 6. +// if return method throws, the thrown value should be ignored. +test(MyArray, { + nextVal: { value: 1, done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + throw "return throws"; + }; + }, + exceptionVal: "defineProperty throws", + closed: true, +}); + +test(MyArray, { + nextVal: { value: 1, done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + return "non object"; + }; + }, + exceptionVal: "defineProperty throws", + closed: true, +}); + +// == Error cases without close == + +// ES 2017 draft 22.1.2.1 step 5.e.iii. +test(Array, { + nextThrowVal: "next throws", + exceptionVal: "next throws", + closed: false, +}); + +test(Array, { + nextVal: { value: {}, get done() { throw "done getter throws"; } }, + exceptionVal: "done getter throws", + closed: false, +}); + +// ES 2017 draft 22.1.2.1 step 5.e.v. +test(Array, { + nextVal: { get value() { throw "value getter throws"; }, done: false }, + exceptionVal: "value getter throws", + closed: false, +}); + +// == Successful cases == + +test(Array, { + nextVal: { value: 1, done: false }, + closed: false, +}); + +if (typeof reportCompare === 'function') + reportCompare(true, true); -- cgit v1.2.3 From 114794557687aebca601c38ba0f0a52a43b44d4a Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 15 Mar 2018 21:12:39 +0100 Subject: Close iterator after error in Promise.{all,race} Issue #17 --- js/src/builtin/Promise.cpp | 53 ++++-- js/src/js.msg | 3 + js/src/jsapi.h | 6 + js/src/tests/ecma_6/Promise/iterator-close.js | 234 ++++++++++++++++++++++++++ js/src/vm/ForOfIterator.cpp | 51 ++++++ 5 files changed, 330 insertions(+), 17 deletions(-) create mode 100644 js/src/tests/ecma_6/Promise/iterator-close.js (limited to 'js/src') diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 59c97e529..c781a336d 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -1369,7 +1369,8 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, HandleObject promiseObj, - HandleObject resolve, HandleObject reject); + HandleObject resolve, HandleObject reject, + bool* done); // ES2016, 25.4.4.1. static bool @@ -1410,12 +1411,14 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) // Step 6 (implicit). // Step 7. - bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject); + bool done; + bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject, &done); // Step 8. if (!result) { // Step 8.a. - // TODO: implement iterator closing. + if (!done) + iter.closeThrow(); // Step 8.b. return AbruptRejectPromise(cx, args, resultPromise, reject); @@ -1598,8 +1601,11 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res // ES2016, 25.4.4.1.1. static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - HandleObject promiseObj, HandleObject resolve, HandleObject reject) + HandleObject promiseObj, HandleObject resolve, HandleObject reject, + bool* done) { + *done = false; + RootedObject unwrappedPromiseObj(cx); if (IsWrapper(promiseObj)) { unwrappedPromiseObj = CheckedUnwrap(promiseObj); @@ -1666,14 +1672,19 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, RootedValue rejectFunVal(cx, ObjectOrNullValue(reject)); while (true) { - bool done; - // Steps a, b, c, e, f, g. - if (!iterator.next(&nextValue, &done)) + // Steps a-c, e-g. + if (!iterator.next(&nextValue, done)) { + // Steps b, f. + *done = true; + + // Steps c, g. return false; + } // Step d. - if (done) { + if (*done) { // Step d.i (implicit). + // Step d.ii. int32_t remainingCount = dataHolder->decreaseRemainingCount(); @@ -1822,7 +1833,8 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, HandleObject promiseObj, - HandleObject resolve, HandleObject reject); + HandleObject resolve, HandleObject reject, + bool* done); // ES2016, 25.4.4.3. static bool @@ -1863,12 +1875,14 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp) // Step 6 (implicit). // Step 7. - bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject); + bool done; + bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject, &done); // Step 8. if (!result) { // Step 8.a. - // TODO: implement iterator closing. + if (!done) + iter.closeThrow(); // Step 8.b. return AbruptRejectPromise(cx, args, resultPromise, reject); @@ -1882,25 +1896,30 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp) // ES2016, 25.4.4.3.1. static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - HandleObject promiseObj, HandleObject resolve, HandleObject reject) + HandleObject promiseObj, HandleObject resolve, HandleObject reject, + bool* done) { + *done = false; MOZ_ASSERT(C->isConstructor()); RootedValue CVal(cx, ObjectValue(*C)); RootedValue nextValue(cx); RootedValue resolveFunVal(cx, ObjectOrNullValue(resolve)); RootedValue rejectFunVal(cx, ObjectOrNullValue(reject)); - bool done; while (true) { // Steps a-c, e-g. - if (!iterator.next(&nextValue, &done)) + if (!iterator.next(&nextValue, done)) { + // Steps b, f. + *done = true; + + // Steps c, g. return false; + } // Step d. - if (done) { - // Step d.i. - // TODO: implement iterator closing. + if (*done) { + // Step d.i (implicit). // Step d.ii. return true; diff --git a/js/src/js.msg b/js/src/js.msg index cb5fc383b..2a818056f 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -579,3 +579,6 @@ MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCa MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.") MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.") MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.") + +// Iterator +MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable") diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 2d6ff462c..a93852fa5 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -6166,6 +6166,12 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) { */ bool next(JS::MutableHandleValue val, bool* done); + /** + * Close the iterator. + * For the case that completion type is throw. + */ + void closeThrow(); + /** * If initialized with throwOnNonCallable = false, check whether * the value is iterable. diff --git a/js/src/tests/ecma_6/Promise/iterator-close.js b/js/src/tests/ecma_6/Promise/iterator-close.js new file mode 100644 index 000000000..f17260d05 --- /dev/null +++ b/js/src/tests/ecma_6/Promise/iterator-close.js @@ -0,0 +1,234 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue + +var BUGNUMBER = 1180306; +var summary = 'Promise.{all,race} should close iterator on error'; + +print(BUGNUMBER + ": " + summary); + +function test(ctor, props, { nextVal=undefined, + nextThrowVal=undefined, + modifier=undefined, + rejectReason=undefined, + rejectType=undefined, + closed=true }) { + function getIterable() { + let iterable = { + closed: false, + [Symbol.iterator]() { + let iterator = { + first: true, + next() { + if (this.first) { + this.first = false; + if (nextThrowVal) + throw nextThrowVal; + return nextVal; + } + return { value: undefined, done: true }; + }, + return() { + iterable.closed = true; + return {}; + } + }; + if (modifier) + modifier(iterator, iterable); + + return iterator; + } + }; + return iterable; + } + for (let prop of props) { + let iterable = getIterable(); + let e; + ctor[prop](iterable).catch(e_ => { e = e_; }); + drainJobQueue(); + if(rejectType) + assertEq(e instanceof rejectType, true); + else + assertEq(e, rejectReason); + assertEq(iterable.closed, closed); + } +} + +// == Error cases with close == + +// ES 2017 draft 25.4.4.1.1 step 6.i. +// ES 2017 draft 25.4.4.3.1 step 3.h. +class MyPromiseStaticResolveGetterThrows extends Promise { + static get resolve() { + throw "static resolve getter throws"; + } +}; +test(MyPromiseStaticResolveGetterThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + rejectReason: "static resolve getter throws", + closed: true, +}); + +class MyPromiseStaticResolveThrows extends Promise { + static resolve() { + throw "static resolve throws"; + } +}; +test(MyPromiseStaticResolveThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + rejectReason: "static resolve throws", + closed: true, +}); + +// ES 2017 draft 25.4.4.1.1 step 6.q. +// ES 2017 draft 25.4.4.3.1 step 3.i. +class MyPromiseThenGetterThrows extends Promise { + static resolve() { + return { + get then() { + throw "then getter throws"; + } + }; + } +}; +test(MyPromiseThenGetterThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + rejectReason: "then getter throws", + closed: true, +}); + +class MyPromiseThenThrows extends Promise { + static resolve() { + return { + then() { + throw "then throws"; + } + }; + } +}; +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + rejectReason: "then throws", + closed: true, +}); + +// ES 2017 draft 7.4.6 step 3. +// if GetMethod fails, the thrown value should be used. +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + throw "return getter throws"; + } + }); + }, + rejectReason: "return getter throws", + closed: true, +}); +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + return "non object"; + } + }); + }, + rejectType: TypeError, + closed: true, +}); +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + Object.defineProperty(iterator, "return", { + get: function() { + iterable.closed = true; + // Non callable. + return {}; + } + }); + }, + rejectType: TypeError, + closed: true, +}); + +// ES 2017 draft 7.4.6 steps 6. +// if return method throws, the thrown value should be ignored. +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + throw "return throws"; + }; + }, + rejectReason: "then throws", + closed: true, +}); + +test(MyPromiseThenThrows, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + modifier: (iterator, iterable) => { + iterator.return = function() { + iterable.closed = true; + return "non object"; + }; + }, + rejectReason: "then throws", + closed: true, +}); + +// == Error cases without close == + +// ES 2017 draft 25.4.4.1.1 step 6.a. +test(Promise, ["all", "race"], { + nextThrowVal: "next throws", + rejectReason: "next throws", + closed: false, +}); + +test(Promise, ["all", "race"], { + nextVal: { value: {}, get done() { throw "done getter throws"; } }, + rejectReason: "done getter throws", + closed: false, +}); + +// ES 2017 draft 25.4.4.1.1 step 6.e. +test(Promise, ["all", "race"], { + nextVal: { get value() { throw "value getter throws"; }, done: false }, + rejectReason: "value getter throws", + closed: false, +}); + +// ES 2017 draft 25.4.4.1.1 step 6.d.iii.2. +let first = true; +class MyPromiseResolveThrows extends Promise { + constructor(executer) { + if (first) { + first = false; + super((resolve, reject) => { + executer(() => { + throw "resolve throws"; + }, reject); + }); + return; + } + super(executer); + } +}; +test(MyPromiseResolveThrows, ["all"], { + nextVal: { value: undefined, done: true }, + rejectReason: "resolve throws", + closed: false, +}); + +// == Successful cases == + +test(Promise, ["all", "race"], { + nextVal: { value: Promise.resolve(1), done: false }, + closed: false, +}); + +if (typeof reportCompare === 'function') + reportCompare(true, true); diff --git a/js/src/vm/ForOfIterator.cpp b/js/src/vm/ForOfIterator.cpp index 7bd521a6a..a67b36774 100644 --- a/js/src/vm/ForOfIterator.cpp +++ b/js/src/vm/ForOfIterator.cpp @@ -151,6 +151,57 @@ ForOfIterator::next(MutableHandleValue vp, bool* done) return GetProperty(cx_, resultObj, resultObj, cx_->names().value, vp); } +// ES 2017 draft 0f10dba4ad18de92d47d421f378233a2eae8f077 7.4.6. +// When completion.[[Type]] is throw. +void +ForOfIterator::closeThrow() +{ + MOZ_ASSERT(iterator); + + RootedValue completionException(cx_); + if (cx_->isExceptionPending()) { + if (!GetAndClearException(cx_, &completionException)) + completionException.setUndefined(); + } + + // Steps 1-2 (implicit) + + // Step 3 (partial). + RootedValue returnVal(cx_); + if (!GetProperty(cx_, iterator, iterator, cx_->names().return_, &returnVal)) + return; + + // Step 4. + if (returnVal.isUndefined()) { + cx_->setPendingException(completionException); + return; + } + + // Step 3 (remaining part) + if (!returnVal.isObject()) { + JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE); + return; + } + RootedObject returnObj(cx_, &returnVal.toObject()); + if (!returnObj->isCallable()) { + JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE); + return; + } + + // Step 5. + RootedValue innerResultValue(cx_); + if (!js::Call(cx_, returnVal, iterator, &innerResultValue)) { + if (cx_->isExceptionPending()) + cx_->clearPendingException(); + } + + // Step 6. + cx_->setPendingException(completionException); + + // Steps 7-9 (skipped). + return; +} + bool ForOfIterator::materializeArrayIterator() { -- cgit v1.2.3 From 28d4e4a5fa5ba7a22d3497769fbb5a9d11db7a9e Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 16 Mar 2018 09:04:55 +0100 Subject: Bug 1318017: Call GetPrototypeFromConstructor for generator/async function and Intl constructors [Depends on] Bug 755821: Function() should use the parser's argument parsing code --- js/src/builtin/Intl.cpp | 102 +++++++++++++-------- js/src/jsfun.cpp | 50 +++++++--- js/src/tests/Intl/Collator/construct-newtarget.js | 81 ++++++++++++++++ .../Intl/DateTimeFormat/construct-newtarget.js | 81 ++++++++++++++++ .../tests/Intl/NumberFormat/construct-newtarget.js | 81 ++++++++++++++++ .../AsyncFunctions/construct-newtarget.js | 79 ++++++++++++++++ js/src/tests/ecma_2017/AsyncFunctions/subclass.js | 31 +++++++ .../tests/ecma_6/Generators/construct-newtarget.js | 79 ++++++++++++++++ js/src/tests/ecma_6/Generators/subclass.js | 33 +++++++ js/src/vm/AsyncFunction.cpp | 19 ++-- js/src/vm/AsyncFunction.h | 3 + 11 files changed, 586 insertions(+), 53 deletions(-) create mode 100644 js/src/tests/Intl/Collator/construct-newtarget.js create mode 100644 js/src/tests/Intl/DateTimeFormat/construct-newtarget.js create mode 100644 js/src/tests/Intl/NumberFormat/construct-newtarget.js create mode 100644 js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js create mode 100644 js/src/tests/ecma_2017/AsyncFunctions/subclass.js create mode 100644 js/src/tests/ecma_6/Generators/construct-newtarget.js create mode 100644 js/src/tests/ecma_6/Generators/subclass.js (limited to 'js/src') diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 990a4acdb..3a20c487b 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -765,42 +765,53 @@ static const JSFunctionSpec collator_methods[] = { }; /** - * Collator constructor. - * Spec: ECMAScript Internationalization API Specification, 10.1 + * 10.1.2 Intl.Collator([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool Collator(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when Collator is called because of + // backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 10.1.2.1 step 3 + // ES Intl 1st ed., 10.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 10.1.2.1 step 4 + // ES Intl 1st ed., 10.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 10.1.2.1 step 5 + // ES Intl 1st ed., 10.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 10.1.2.1 step 3.a + // ES Intl 1st ed., 10.1.2.1 step 3.a construct = true; } } if (construct) { - // 10.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx)); - if (!proto) + // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateCollatorPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &CollatorClass, proto); if (!obj) return false; @@ -808,15 +819,13 @@ Collator(JSContext* cx, const CallArgs& args, bool construct) obj->as().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); } - // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 10.1.2.1 step 6; 10.1.3.1 step 3 + // Step 6. if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options)) return false; - // 10.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -833,6 +842,7 @@ js::intl_Collator(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot // be used with "new", but it still has to be treated as a constructor. return Collator(cx, args, true); @@ -1257,42 +1267,53 @@ static const JSFunctionSpec numberFormat_methods[] = { }; /** - * NumberFormat constructor. - * Spec: ECMAScript Internationalization API Specification, 11.1 + * 11.2.1 Intl.NumberFormat([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when NumberFormat is called + // because of backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 11.1.2.1 step 3 + // ES Intl 1st ed., 11.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 11.1.2.1 step 4 + // ES Intl 1st ed., 11.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 11.1.2.1 step 5 + // ES Intl 1st ed., 11.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 11.1.2.1 step 3.a + // ES Intl 1st ed., 11.1.2.1 step 3.a construct = true; } } if (construct) { - // 11.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx)); - if (!proto) + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateNumberFormatPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto); if (!obj) return false; @@ -1300,15 +1321,13 @@ NumberFormat(JSContext* cx, const CallArgs& args, bool construct) obj->as().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); } - // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 11.1.2.1 step 6; 11.1.3.1 step 3 + // Step 3. if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options)) return false; - // 11.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -1325,6 +1344,7 @@ js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it // cannot be used with "new", but it still has to be treated as a // constructor. @@ -1725,42 +1745,53 @@ static const JSFunctionSpec dateTimeFormat_methods[] = { }; /** - * DateTimeFormat constructor. - * Spec: ECMAScript Internationalization API Specification, 12.1 + * 12.2.1 Intl.DateTimeFormat([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when DateTimeFormat is called + // because of backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 12.1.2.1 step 3 + // ES Intl 1st ed., 12.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 12.1.2.1 step 4 + // ES Intl 1st ed., 12.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 12.1.2.1 step 5 + // ES Intl 1st ed., 12.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 12.1.2.1 step 3.a + // ES Intl 1st ed., 12.1.2.1 step 3.a construct = true; } } if (construct) { - // 12.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx)); - if (!proto) + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateDateTimeFormatPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto); if (!obj) return false; @@ -1768,15 +1799,13 @@ DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct) obj->as().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); } - // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 12.1.2.1 step 6; 12.1.3.1 step 3 + // Step 3. if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options)) return false; - // 12.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -1793,6 +1822,7 @@ js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it // cannot be used with "new", but it still has to be treated as a // constructor. diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index c952441ad..9be02b217 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1682,11 +1682,9 @@ const JSFunctionSpec js::function_methods[] = { }; static bool -FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind generatorKind, +FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generatorKind, FunctionAsyncKind asyncKind) { - CallArgs args = CallArgsFromVp(argc, vp); - /* Block this call if security callbacks forbid it. */ Rooted global(cx, &args.callee().global()); if (!GlobalObject::isRuntimeCodeGenEnabled(cx, global)) { @@ -1803,16 +1801,23 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener * and so would a call to f from another top-level's script or function. */ RootedAtom anonymousAtom(cx, cx->names().anonymous); + + // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 + // 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction, step 24. RootedObject proto(cx); - if (isStarGenerator) { + if (!isAsync) { + if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) + return false; + } + + // 19.2.1.1.1, step 4.d, use %Generator% as the fallback prototype. + // Also use %Generator% for the unwrapped function of async functions. + if (!proto && isStarGenerator) { // Unwrapped function of async function should use GeneratorFunction, // while wrapped function isn't generator. proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global); if (!proto) return false; - } else { - if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) - return false; } RootedObject globalLexical(cx, &global->lexicalEnvironment()); @@ -1921,24 +1926,47 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener bool js::Function(JSContext* cx, unsigned argc, Value* vp) { - return FunctionConstructor(cx, argc, vp, NotGenerator, SyncFunction); + CallArgs args = CallArgsFromVp(argc, vp); + return FunctionConstructor(cx, args, NotGenerator, SyncFunction); } bool js::Generator(JSContext* cx, unsigned argc, Value* vp) { - return FunctionConstructor(cx, argc, vp, StarGenerator, SyncFunction); + CallArgs args = CallArgsFromVp(argc, vp); + return FunctionConstructor(cx, args, StarGenerator, SyncFunction); } bool js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (!FunctionConstructor(cx, argc, vp, StarGenerator, AsyncFunction)) + + // Save the callee before its reset in FunctionConstructor(). + RootedObject newTarget(cx); + if (args.isConstructing()) + newTarget = &args.newTarget().toObject(); + else + newTarget = &args.callee(); + + if (!FunctionConstructor(cx, args, StarGenerator, AsyncFunction)) return false; + // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 + // 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction, step 24. + RootedObject proto(cx); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return false; + + // 19.2.1.1.1, step 4.d, use %AsyncFunctionPrototype% as the fallback. + if (!proto) { + proto = GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()); + if (!proto) + return false; + } + RootedFunction unwrapped(cx, &args.rval().toObject().as()); - RootedObject wrapped(cx, WrapAsyncFunction(cx, unwrapped)); + RootedObject wrapped(cx, WrapAsyncFunctionWithProto(cx, unwrapped, proto)); if (!wrapped) return false; diff --git a/js/src/tests/Intl/Collator/construct-newtarget.js b/js/src/tests/Intl/Collator/construct-newtarget.js new file mode 100644 index 000000000..5db1abf37 --- /dev/null +++ b/js/src/tests/Intl/Collator/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +// Test subclassing %Intl.Collator% works correctly. +class MyCollator extends Intl.Collator {} + +var obj = new MyCollator(); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, []); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, [], MyCollator); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, [], Intl.Collator); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyCollator, [], Array); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.Collator, [], Array); +assertEq(obj instanceof Intl.Collator, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %CollatorPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.Collator, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + +obj = Reflect.construct(MyCollator, [], NewTargetNullPrototype); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.Collator, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.Collator, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js b/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js new file mode 100644 index 000000000..bbc08c79a --- /dev/null +++ b/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +// Test subclassing %Intl.DateTimeFormat% works correctly. +class MyDateTimeFormat extends Intl.DateTimeFormat {} + +var obj = new MyDateTimeFormat(); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, []); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], MyDateTimeFormat); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], Intl.DateTimeFormat); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyDateTimeFormat, [], Array); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.DateTimeFormat, [], Array); +assertEq(obj instanceof Intl.DateTimeFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %DateTimeFormatPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.DateTimeFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.DateTimeFormat, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.DateTimeFormat, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/Intl/NumberFormat/construct-newtarget.js b/js/src/tests/Intl/NumberFormat/construct-newtarget.js new file mode 100644 index 000000000..0053b2737 --- /dev/null +++ b/js/src/tests/Intl/NumberFormat/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +// Test subclassing %Intl.NumberFormat% works correctly. +class MyNumberFormat extends Intl.NumberFormat {} + +var obj = new MyNumberFormat(); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, []); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], MyNumberFormat); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], Intl.NumberFormat); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyNumberFormat, [], Array); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.NumberFormat, [], Array); +assertEq(obj instanceof Intl.NumberFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %NumberFormatPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.NumberFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.NumberFormat, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.NumberFormat, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js new file mode 100644 index 000000000..7d75d0c93 --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const AsyncFunction = async function(){}.constructor; + + +// Test subclassing %AsyncFunction% works correctly. +class MyAsync extends AsyncFunction {} + +var fn = new MyAsync(); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, []); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, [], MyAsync); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, [], AsyncFunction); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +// Set a different constructor as NewTarget. +fn = Reflect.construct(MyAsync, [], Array); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + +fn = Reflect.construct(AsyncFunction, [], Array); +assertEq(fn instanceof AsyncFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + + +// The prototype defaults to %AsyncFunctionPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +fn = Reflect.construct(AsyncFunction, [], NewTargetNullPrototype); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + +fn = Reflect.construct(MyAsync, [], NewTargetNullPrototype); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(AsyncFunction, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +fn = Reflect.construct(AsyncFunction, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/subclass.js b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js new file mode 100644 index 000000000..da20ab19b --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js @@ -0,0 +1,31 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const AsyncFunction = async function(){}.constructor; + +class MyAsync extends AsyncFunction {} + +// MyGen inherits from %AsyncFunction%. +assertEq(Object.getPrototypeOf(MyAsync), AsyncFunction); + +// MyGen.prototype inherits from %AsyncFunctionPrototype%. +assertEq(Object.getPrototypeOf(MyAsync.prototype), AsyncFunction.prototype); + +var fn = new MyAsync("return await 'ok';"); + +// fn inherits from MyAsync.prototype. +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +// Ensure the new async function can be executed. +var promise = fn(); + +// promise inherits from %Promise.prototype%. +assertEq(Object.getPrototypeOf(promise), Promise.prototype); + +// Computes the expected result. +assertEventuallyEq(promise, "ok"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Generators/construct-newtarget.js b/js/src/tests/ecma_6/Generators/construct-newtarget.js new file mode 100644 index 000000000..f2087852b --- /dev/null +++ b/js/src/tests/ecma_6/Generators/construct-newtarget.js @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const GeneratorFunction = function*(){}.constructor; + + +// Test subclassing %GeneratorFunction% works correctly. +class MyGenerator extends GeneratorFunction {} + +var fn = new MyGenerator(); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, []); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, [], MyGenerator); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, [], GeneratorFunction); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +// Set a different constructor as NewTarget. +fn = Reflect.construct(MyGenerator, [], Array); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + +fn = Reflect.construct(GeneratorFunction, [], Array); +assertEq(fn instanceof GeneratorFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + + +// The prototype defaults to %GeneratorFunctionPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +fn = Reflect.construct(GeneratorFunction, [], NewTargetNullPrototype); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + +fn = Reflect.construct(MyGenerator, [], NewTargetNullPrototype); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(GeneratorFunction, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +fn = Reflect.construct(GeneratorFunction, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Generators/subclass.js b/js/src/tests/ecma_6/Generators/subclass.js new file mode 100644 index 000000000..f93f4df4d --- /dev/null +++ b/js/src/tests/ecma_6/Generators/subclass.js @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const GeneratorFunction = function*(){}.constructor; + +class MyGen extends GeneratorFunction {} + +// MyGen inherits from %GeneratorFunction%. +assertEq(Object.getPrototypeOf(MyGen), GeneratorFunction); + +// MyGen.prototype inherits from %Generator%. +assertEq(Object.getPrototypeOf(MyGen.prototype), GeneratorFunction.prototype); + +var fn = new MyGen("yield* [1, 2, 3]"); + +// fn inherits from MyGen.prototype. +assertEq(Object.getPrototypeOf(fn), MyGen.prototype); + +// fn.prototype inherits from %GeneratorPrototype%. +assertEq(Object.getPrototypeOf(fn.prototype), GeneratorFunction.prototype.prototype); + +// Ensure the new generator function can be executed. +var it = fn(); + +// it inherits from fn.prototype. +assertEq(Object.getPrototypeOf(it), fn.prototype); + +// Computes the expected result. +assertEqArray([...it], [1, 2, 3]); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index bd0b4f32a..1e0c7d7c2 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -107,18 +107,15 @@ WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp) // the async function's body, replacing `await` with `yield`. `wrapped` is a // function that is visible to the outside, and handles yielded values. JSObject* -js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) +js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto) { MOZ_ASSERT(unwrapped->isStarGenerator()); + MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default" + "%FunctionPrototype% fallback in NewFunctionWithProto()."); // Create a new function with AsyncFunctionPrototype, reusing the name and // the length of `unwrapped`. - // Step 1. - RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global())); - if (!proto) - return nullptr; - RootedAtom funName(cx, unwrapped->name()); uint16_t length; if (!unwrapped->getLength(cx, &length)) @@ -141,6 +138,16 @@ js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) return wrapped; } +JSObject* +js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) +{ + RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global())); + if (!proto) + return nullptr; + + return WrapAsyncFunctionWithProto(cx, unwrapped, proto); +} + enum class ResumeKind { Normal, Throw diff --git a/js/src/vm/AsyncFunction.h b/js/src/vm/AsyncFunction.h index ddf81a177..d7f2c1311 100644 --- a/js/src/vm/AsyncFunction.h +++ b/js/src/vm/AsyncFunction.h @@ -21,6 +21,9 @@ GetUnwrappedAsyncFunction(JSFunction* wrapped); bool IsWrappedAsyncFunction(JSFunction* fun); +JSObject* +WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto); + JSObject* WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped); -- cgit v1.2.3 From af300f36f11293c12f2ee01580fc749a7e114376 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 16 Mar 2018 11:35:57 +0100 Subject: Bug 755821: Function() should use the parser's argument parsing code --- js/src/frontend/BytecodeCompiler.cpp | 127 ++++------ js/src/frontend/BytecodeCompiler.h | 41 +++- js/src/frontend/Parser.cpp | 55 ++--- js/src/frontend/Parser.h | 16 +- .../jit-test/tests/basic/destructuring-default.js | 5 +- js/src/jit-test/tests/basic/destructuring-rest.js | 7 +- js/src/jit-test/tests/debug/Script-gc-02.js | 2 +- js/src/jit-test/tests/debug/Script-gc-03.js | 2 +- .../jit-test/tests/debug/Script-sourceStart-04.js | 4 +- js/src/js.msg | 1 + js/src/jsapi.cpp | 70 ++++-- js/src/jsfun.cpp | 264 +++++---------------- js/src/jsfun.h | 5 +- js/src/jsscript.cpp | 27 ++- js/src/jsscript.h | 28 ++- .../ecma_6/Function/invalid-parameter-list.js | 27 +++ js/src/vm/Debugger.cpp | 9 +- js/src/wasm/AsmJS.cpp | 75 +----- 18 files changed, 314 insertions(+), 451 deletions(-) create mode 100644 js/src/tests/ecma_6/Function/invalid-parameter-list.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index d4c758b6c..3fbfdaa1a 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -7,6 +7,7 @@ #include "frontend/BytecodeCompiler.h" #include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Maybe.h" #include "jscntxt.h" #include "jsscript.h" @@ -28,6 +29,7 @@ using namespace js; using namespace js::frontend; using mozilla::Maybe; +using mozilla::Nothing; class MOZ_STACK_CLASS AutoCompilationTraceLogger { @@ -57,24 +59,24 @@ class MOZ_STACK_CLASS BytecodeCompiler // Call setters for optional arguments. void maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor); - void setSourceArgumentsNotIncluded(); JSScript* compileGlobalScript(ScopeKind scopeKind); JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope); ModuleObject* compileModule(); - bool compileFunctionBody(MutableHandleFunction fun, Handle formals, - GeneratorKind generatorKind, FunctionAsyncKind asyncKind); + bool compileStandaloneFunction(MutableHandleFunction fun, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Maybe parameterListEnd); ScriptSourceObject* sourceObjectPtr() const; private: JSScript* compileScript(HandleObject environment, SharedContext* sc); bool checkLength(); - bool createScriptSource(); + bool createScriptSource(Maybe parameterListEnd); bool maybeCompressSource(); bool canLazilyParse(); bool createParser(); - bool createSourceAndParser(); + bool createSourceAndParser(Maybe parameterListEnd = Nothing()); bool createScript(); bool emplaceEmitter(Maybe& emitter, SharedContext* sharedContext); bool handleParseFailure(const Directives& newDirectives); @@ -90,7 +92,6 @@ class MOZ_STACK_CLASS BytecodeCompiler SourceBufferHolder& sourceBuffer; RootedScope enclosingScope; - bool sourceArgumentsNotIncluded; RootedScriptSource sourceObject; ScriptSource* scriptSource; @@ -130,7 +131,6 @@ BytecodeCompiler::BytecodeCompiler(ExclusiveContext* cx, options(options), sourceBuffer(sourceBuffer), enclosingScope(cx, enclosingScope), - sourceArgumentsNotIncluded(false), sourceObject(cx), scriptSource(nullptr), sourceCompressor(nullptr), @@ -147,12 +147,6 @@ BytecodeCompiler::maybeSetSourceCompressor(SourceCompressionTask* sourceCompress this->sourceCompressor = sourceCompressor; } -void -BytecodeCompiler::setSourceArgumentsNotIncluded() -{ - sourceArgumentsNotIncluded = true; -} - bool BytecodeCompiler::checkLength() { @@ -169,12 +163,12 @@ BytecodeCompiler::checkLength() } bool -BytecodeCompiler::createScriptSource() +BytecodeCompiler::createScriptSource(Maybe parameterListEnd) { if (!checkLength()) return false; - sourceObject = CreateScriptSourceObject(cx, options); + sourceObject = CreateScriptSourceObject(cx, options, parameterListEnd); if (!sourceObject) return false; @@ -193,9 +187,7 @@ BytecodeCompiler::maybeCompressSource() if (!cx->compartment()->behaviors().discardSource()) { if (options.sourceIsLazy) { scriptSource->setSourceRetrievable(); - } else if (!scriptSource->setSourceCopy(cx, sourceBuffer, sourceArgumentsNotIncluded, - sourceCompressor)) - { + } else if (!scriptSource->setSourceCopy(cx, sourceBuffer, sourceCompressor)) { return false; } } @@ -242,9 +234,9 @@ BytecodeCompiler::createParser() } bool -BytecodeCompiler::createSourceAndParser() +BytecodeCompiler::createSourceAndParser(Maybe parameterListEnd /* = Nothing() */) { - return createScriptSource() && + return createScriptSource(parameterListEnd) && maybeCompressSource() && createParser(); } @@ -432,18 +424,19 @@ BytecodeCompiler::compileModule() return module; } +// Compile a standalone JS function, which might appear as the value of an +// event handler attribute in an HTML tag, or in a Function() +// constructor. bool -BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun, - Handle formals, - GeneratorKind generatorKind, - FunctionAsyncKind asyncKind) +BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Maybe parameterListEnd) { MOZ_ASSERT(fun); MOZ_ASSERT(fun->isTenured()); - fun->setArgCount(formals.length()); - - if (!createSourceAndParser()) + if (!createSourceAndParser(parameterListEnd)) return false; // Speculatively parse using the default directives implied by the context. @@ -454,8 +447,8 @@ BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun, ParseNode* fn; do { Directives newDirectives = directives; - fn = parser->standaloneFunctionBody(fun, enclosingScope, formals, generatorKind, asyncKind, - directives, &newDirectives); + fn = parser->standaloneFunction(fun, enclosingScope, parameterListEnd, generatorKind, + asyncKind, directives, &newDirectives); if (!fn && !handleParseFailure(newDirectives)) return false; } while (!fn); @@ -492,14 +485,15 @@ BytecodeCompiler::sourceObjectPtr() const } ScriptSourceObject* -frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options) +frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + Maybe parameterListEnd /* = Nothing() */) { ScriptSource* ss = cx->new_(); if (!ss) return nullptr; ScriptSourceHolder ssHolder(ss); - if (!ss->initFromOptions(cx, options)) + if (!ss->initFromOptions(cx, options, parameterListEnd)) return nullptr; RootedScriptSource sso(cx, ScriptSourceObject::create(cx, ss)); @@ -676,63 +670,44 @@ frontend::CompileLazyFunction(JSContext* cx, Handle lazy, const cha return bce.emitFunctionScript(pn->pn_body); } -// Compile a JS function body, which might appear as the value of an event -// handler attribute in an HTML tag, or in a Function() constructor. -static bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, - Handle formals, SourceBufferHolder& srcBuf, - HandleScope enclosingScope, GeneratorKind generatorKind, - FunctionAsyncKind asyncKind) +bool +frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe parameterListEnd, + HandleScope enclosingScope /* = nullptr */) { - MOZ_ASSERT(!options.isRunOnce); - - // FIXME: make Function pass in two strings and parse them as arguments and - // ProgramElements respectively. + RootedScope scope(cx, enclosingScope); + if (!scope) + scope = &cx->global()->emptyGlobalScope(); - BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, enclosingScope, + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, scope, TraceLogger_ParserCompileFunction); - compiler.setSourceArgumentsNotIncluded(); - return compiler.compileFunctionBody(fun, formals, generatorKind, asyncKind); + return compiler.compileStandaloneFunction(fun, NotGenerator, SyncFunction, parameterListEnd); } bool -frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle formals, JS::SourceBufferHolder& srcBuf, - HandleScope enclosingScope) -{ - return CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingScope, NotGenerator, - SyncFunction); -} - -bool -frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle formals, JS::SourceBufferHolder& srcBuf) +frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe parameterListEnd) { RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - NotGenerator, SyncFunction); -} -bool -frontend::CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle formals, - JS::SourceBufferHolder& srcBuf) -{ - RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - StarGenerator, SyncFunction); + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, + TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, StarGenerator, SyncFunction, parameterListEnd); } bool -frontend::CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle formals, - JS::SourceBufferHolder& srcBuf) +frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe parameterListEnd) { RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - StarGenerator, AsyncFunction); + + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, + TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, StarGenerator, AsyncFunction, parameterListEnd); } diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 1d86f1160..72e967639 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -7,6 +7,8 @@ #ifndef frontend_BytecodeCompiler_h #define frontend_BytecodeCompiler_h +#include "mozilla/Maybe.h" + #include "NamespaceImports.h" #include "vm/Scope.h" @@ -51,22 +53,36 @@ CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, MOZ_MUST_USE bool CompileLazyFunction(JSContext* cx, Handle lazy, const char16_t* chars, size_t length); +// +// Compile a single function. The source in srcBuf must match the ECMA-262 +// FunctionExpression production. +// +// If nonzero, parameterListEnd is the offset within srcBuf where the parameter +// list is expected to end. During parsing, if we find that it ends anywhere +// else, it's a SyntaxError. This is used to implement the Function constructor; +// it's how we detect that these weird cases are SyntaxErrors: +// +// Function("/*", "*/x) {") +// Function("x){ if (3", "return x;}") +// MOZ_MUST_USE bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle formals, JS::SourceBufferHolder& srcBuf, - HandleScope enclosingScope); +CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe parameterListEnd, + HandleScope enclosingScope = nullptr); -// As above, but defaults to the global lexical scope as the enclosing scope. MOZ_MUST_USE bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle formals, JS::SourceBufferHolder& srcBuf); +CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe parameterListEnd); MOZ_MUST_USE bool -CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle formals, JS::SourceBufferHolder& srcBuf); +CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe parameterListEnd); MOZ_MUST_USE bool CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, @@ -74,7 +90,8 @@ CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, Handle formals, JS::SourceBufferHolder& srcBuf); ScriptSourceObject* -CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options); +CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + mozilla::Maybe parameterListEnd = mozilla::Nothing()); /* * True if str consists of an IdentifierStart character, followed by one or diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 49fef2bf9..f42546eb5 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -2245,13 +2245,13 @@ GetYieldHandling(GeneratorKind generatorKind, FunctionAsyncKind asyncKind) template <> ParseNode* -Parser::standaloneFunctionBody(HandleFunction fun, - HandleScope enclosingScope, - Handle formals, - GeneratorKind generatorKind, - FunctionAsyncKind asyncKind, - Directives inheritedDirectives, - Directives* newDirectives) +Parser::standaloneFunction(HandleFunction fun, + HandleScope enclosingScope, + Maybe parameterListEnd, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Directives inheritedDirectives, + Directives* newDirectives) { MOZ_ASSERT(checkOptionsCalled); @@ -2274,25 +2274,14 @@ Parser::standaloneFunctionBody(HandleFunction fun, if (!funpc.init()) return null(); funpc.setIsStandaloneFunctionBody(); - funpc.functionScope().useAsVarScope(&funpc); - - if (formals.length() >= ARGNO_LIMIT) { - report(ParseError, false, null(), JSMSG_TOO_MANY_FUN_ARGS); - return null(); - } - - bool duplicatedParam = false; - for (uint32_t i = 0; i < formals.length(); i++) { - if (!notePositionalFormalParameter(fn, formals[i], false, &duplicatedParam)) - return null(); - } - funbox->hasDuplicateParameters = duplicatedParam; YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction); - ParseNode* pn = functionBody(InAllowed, yieldHandling, Statement, StatementListBody); - if (!pn) + if (!functionFormalParametersAndBody(InAllowed, yieldHandling, fn, Statement, + parameterListEnd)) + { return null(); + } TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) @@ -2303,15 +2292,7 @@ Parser::standaloneFunctionBody(HandleFunction fun, return null(); } - if (!FoldConstants(context, &pn, this)) - return null(); - - fn->pn_pos.end = pos().end; - - MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY)); - fn->pn_body->append(pn); - - if (!finishFunction()) + if (!FoldConstants(context, &fn, this)) return null(); return fn; @@ -3415,7 +3396,8 @@ template bool Parser::functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling, - Node pn, FunctionSyntaxKind kind) + Node pn, FunctionSyntaxKind kind, + Maybe parameterListEnd /* = Nothing() */) { // Given a properly initialized parse context, try to parse an actual // function without concern for conversion to strict mode, use of lazy @@ -3447,6 +3429,13 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, } } + // When parsing something for new Function() we have to make sure to + // only treat a certain part of the source as a parameter list. + if (parameterListEnd.isSome() && parameterListEnd.value() != pos().begin) { + report(ParseError, false, null(), JSMSG_UNEXPECTED_PARAMLIST_END); + return false; + } + // Parse the function body. FunctionBodyType bodyType = StatementListBody; TokenKind tt; @@ -3502,7 +3491,7 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, report(ParseError, false, null(), JSMSG_CURLY_AFTER_BODY); return false; } - funbox->bufEnd = pos().begin + 1; + funbox->bufEnd = pos().end; } else { #if !JS_HAS_EXPR_CLOSURES MOZ_ASSERT(kind == Arrow); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 0ad4d56a0..b58b021cd 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1020,12 +1020,12 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // Parse a module. Node moduleBody(ModuleSharedContext* modulesc); - // Parse a function, given only its body. Used for the Function and - // Generator constructors. - Node standaloneFunctionBody(HandleFunction fun, HandleScope enclosingScope, - Handle formals, - GeneratorKind generatorKind, FunctionAsyncKind asyncKind, - Directives inheritedDirectives, Directives* newDirectives); + // Parse a function, used for the Function, GeneratorFunction, and + // AsyncFunction constructors. + Node standaloneFunction(HandleFunction fun, HandleScope enclosingScope, + mozilla::Maybe parameterListEnd, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + Directives inheritedDirectives, Directives* newDirectives); // Parse a function, given only its arguments and body. Used for lazily // parsed functions. @@ -1041,7 +1041,9 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // Parse a function's formal parameters and its body assuming its function // ParseContext is already on the stack. bool functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling, - Node pn, FunctionSyntaxKind kind); + Node pn, FunctionSyntaxKind kind, + mozilla::Maybe parameterListEnd = mozilla::Nothing()); + // Determine whether |yield| is a valid name in the current context, or // whether it's prohibited due to strictness, JS version, or occurrence diff --git a/js/src/jit-test/tests/basic/destructuring-default.js b/js/src/jit-test/tests/basic/destructuring-default.js index 168977e80..49a908b8a 100644 --- a/js/src/jit-test/tests/basic/destructuring-default.js +++ b/js/src/jit-test/tests/basic/destructuring-default.js @@ -84,10 +84,7 @@ function testArgumentFunction(pattern, input) { 'return [a, b, c, d, e, f];' )(input); } -// XXX: ES6 requires the `Function` constructor to accept arbitrary -// `BindingElement`s as formal parameters. See Bug 1037939. -// Once fixed, please update the assertions below. -assertThrowsInstanceOf(() => testAll(testArgumentFunction), SyntaxError); +testAll(testArgumentFunction); function testThrow(pattern, input) { return new Function('input', diff --git a/js/src/jit-test/tests/basic/destructuring-rest.js b/js/src/jit-test/tests/basic/destructuring-rest.js index f53f07e03..fcb7b79bb 100644 --- a/js/src/jit-test/tests/basic/destructuring-rest.js +++ b/js/src/jit-test/tests/basic/destructuring-rest.js @@ -132,10 +132,9 @@ function testArgumentFunction(pattern, input, binding) { 'return ' + binding )(input); } -// XXX: ES6 requires the `Function` constructor to accept arbitrary -// `BindingElement`s as formal parameters. See Bug 1037939. -// Once fixed, please update the assertions below. -assertThrowsInstanceOf(() => testDeclaration(testArgumentFunction), SyntaxError); +// ES6 requires the `Function` constructor to accept arbitrary +// `BindingElement`s as formal parameters. +testDeclaration(testArgumentFunction); function testThrow(pattern, input, binding) { binding = binding || 'rest'; diff --git a/js/src/jit-test/tests/debug/Script-gc-02.js b/js/src/jit-test/tests/debug/Script-gc-02.js index 33d33dfc1..04dd4b220 100644 --- a/js/src/jit-test/tests/debug/Script-gc-02.js +++ b/js/src/jit-test/tests/debug/Script-gc-02.js @@ -10,5 +10,5 @@ assertEq(arr.length, 10); gc(); for (var i = 0; i < arr.length; i++) - assertEq(arr[i].lineCount, 1); + assertEq(arr[i].lineCount, 3); diff --git a/js/src/jit-test/tests/debug/Script-gc-03.js b/js/src/jit-test/tests/debug/Script-gc-03.js index b2cb70232..30c3e8dbc 100644 --- a/js/src/jit-test/tests/debug/Script-gc-03.js +++ b/js/src/jit-test/tests/debug/Script-gc-03.js @@ -10,6 +10,6 @@ assertEq(arr.length, 100); gc(g); for (var i = 0; i < arr.length; i++) - assertEq(arr[i].lineCount, 1); + assertEq(arr[i].lineCount, 3); gc(); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-04.js b/js/src/jit-test/tests/debug/Script-sourceStart-04.js index c12e669bf..2aa382b7b 100644 --- a/js/src/jit-test/tests/debug/Script-sourceStart-04.js +++ b/js/src/jit-test/tests/debug/Script-sourceStart-04.js @@ -20,6 +20,6 @@ function test(string, range) { } test("eval('2 * 3')", [0, 5]); -test("new Function('2 * 3')", [0, 5]); -test("new Function('x', 'x * x')", [0, 5]); +test("new Function('2 * 3')", [0, 12]); +test("new Function('x', 'x * x')", [0, 13]); assertEq(count, 6); diff --git a/js/src/js.msg b/js/src/js.msg index 2a818056f..8766bfbf5 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -329,6 +329,7 @@ MSG_DEF(JSMSG_TOO_MANY_LOCALS, 0, JSEXN_SYNTAXERR, "too many local varia MSG_DEF(JSMSG_TOO_MANY_YIELDS, 0, JSEXN_SYNTAXERR, "too many yield expressions") MSG_DEF(JSMSG_TOUGH_BREAK, 0, JSEXN_SYNTAXERR, "unlabeled break must be inside loop or switch") MSG_DEF(JSMSG_UNEXPECTED_TOKEN, 2, JSEXN_SYNTAXERR, "expected {0}, got {1}") +MSG_DEF(JSMSG_UNEXPECTED_PARAMLIST_END,0, JSEXN_SYNTAXERR, "unexpected end of function parameter list") MSG_DEF(JSMSG_UNNAMED_CLASS_STMT, 0, JSEXN_SYNTAXERR, "class statement requires a name") MSG_DEF(JSMSG_UNNAMED_FUNCTION_STMT, 0, JSEXN_SYNTAXERR, "function statement requires a name") MSG_DEF(JSMSG_UNTERMINATED_COMMENT, 0, JSEXN_SYNTAXERR, "unterminated comment") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ee9c61059..e6fc1f98b 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -11,6 +11,7 @@ #include "jsapi.h" #include "mozilla/FloatingPoint.h" +#include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include "mozilla/Sprintf.h" @@ -107,6 +108,7 @@ using namespace js::gc; using mozilla::Maybe; using mozilla::PodCopy; using mozilla::PodZero; +using mozilla::Some; using JS::AutoGCRooter; using JS::ToInt32; @@ -4232,15 +4234,15 @@ JS_GetFunctionScript(JSContext* cx, HandleFunction fun) } /* - * enclosingScope is a static enclosing scope, if any (e.g. a WithScope). If - * the enclosing scope is the global scope, this must be null. + * enclosingScope is a scope, if any (e.g. a WithScope). If the scope is the + * global scope, this must be null. * - * enclosingDynamicScope is a dynamic scope to use, if it's not the global. + * enclosingEnv is an environment to use, if it's not the global. */ static bool CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, - const char* name, unsigned nargs, const char* const* argnames, - SourceBufferHolder& srcBuf, + const char* name, + SourceBufferHolder& srcBuf, uint32_t parameterListEnd, HandleObject enclosingEnv, HandleScope enclosingScope, MutableHandleFunction fun) { @@ -4256,13 +4258,6 @@ CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, return false; } - Rooted formals(cx, PropertyNameVector(cx)); - for (unsigned i = 0; i < nargs; i++) { - RootedAtom argAtom(cx, Atomize(cx, argnames[i], strlen(argnames[i]))); - if (!argAtom || !formals.append(argAtom->asPropertyName())) - return false; - } - fun.set(NewScriptedFunction(cx, 0, JSFunction::INTERPRETED_NORMAL, funAtom, /* proto = */ nullptr, gc::AllocKind::FUNCTION, TenuredObject, @@ -4275,7 +4270,45 @@ CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(enclosingEnv), enclosingScope->hasOnChain(ScopeKind::NonSyntactic)); - if (!frontend::CompileFunctionBody(cx, fun, optionsArg, formals, srcBuf, enclosingScope)) + if (!frontend::CompileStandaloneFunction(cx, fun, optionsArg, srcBuf, + Some(parameterListEnd), enclosingScope)) + { + return false; + } + + return true; +} + +static MOZ_MUST_USE bool +BuildFunctionString(unsigned nargs, const char* const* argnames, + const SourceBufferHolder& srcBuf, StringBuffer* out, + uint32_t* parameterListEnd) +{ + MOZ_ASSERT(out); + MOZ_ASSERT(parameterListEnd); + + if (!out->ensureTwoByteChars()) + return false; + if (!out->append("(")) + return false; + for (unsigned i = 0; i < nargs; i++) { + if (i != 0) { + if (!out->append(", ")) + return false; + } + if (!out->append(argnames[i], strlen(argnames[i]))) + return false; + } + + // Remember the position of ")". + *parameterListEnd = out->length(); + MOZ_ASSERT(FunctionConstructorMedialSigils[0] == ')'); + + if (!out->append(FunctionConstructorMedialSigils)) + return false; + if (!out->append(srcBuf.get(), srcBuf.length())) + return false; + if (!out->append(FunctionConstructorFinalBrace)) return false; return true; @@ -4291,7 +4324,16 @@ JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain, RootedScope scope(cx); if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env, &scope)) return false; - return CompileFunction(cx, options, name, nargs, argnames, srcBuf, env, scope, fun); + + uint32_t parameterListEnd; + StringBuffer funStr(cx); + if (!BuildFunctionString(nargs, argnames, srcBuf, &funStr, ¶meterListEnd)) + return false; + + size_t newLen = funStr.length(); + SourceBufferHolder newSrcBuf(funStr.stealChars(), newLen, SourceBufferHolder::GiveOwnership); + + return CompileFunction(cx, options, name, newSrcBuf, parameterListEnd, env, scope, fun); } JS_PUBLIC_API(bool) diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 9be02b217..1e1b76d5d 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -12,6 +12,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/CheckedInt.h" +#include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include "mozilla/Range.h" @@ -60,8 +61,10 @@ using namespace js::gc; using namespace js::frontend; using mozilla::ArrayLength; +using mozilla::Maybe; using mozilla::PodCopy; using mozilla::RangedPtr; +using mozilla::Some; static bool fun_enumerate(JSContext* cx, HandleObject obj) @@ -985,19 +988,19 @@ js::FindBody(JSContext* cx, HandleFunction fun, HandleLinearString src, size_t* } JSString* -js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) +js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) { if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx)) return nullptr; if (IsAsmJSModule(fun)) - return AsmJSModuleToString(cx, fun, !lambdaParen); + return AsmJSModuleToString(cx, fun, !prettyPrint); if (IsAsmJSFunction(fun)) return AsmJSFunctionToString(cx, fun); if (IsWrappedAsyncFunction(fun)) { RootedFunction unwrapped(cx, GetUnwrappedAsyncFunction(fun)); - return FunctionToString(cx, unwrapped, lambdaParen); + return FunctionToString(cx, unwrapped, prettyPrint); } StringBuffer out(cx); @@ -1023,11 +1026,10 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) bool funIsMethodOrNonArrowLambda = (fun->isLambda() && !fun->isArrow()) || fun->isMethod() || fun->isGetter() || fun->isSetter(); + bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin(); // If we're not in pretty mode, put parentheses around lambda functions and methods. - if (fun->isInterpreted() && !lambdaParen && funIsMethodOrNonArrowLambda && - !fun->isSelfHostedBuiltin()) - { + if (haveSource && !prettyPrint && funIsMethodOrNonArrowLambda) { if (!out.append("(")) return nullptr; } @@ -1045,7 +1047,6 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) return nullptr; } - bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin(); if (haveSource && !script->scriptSource()->hasSourceData() && !JSScript::loadSource(cx, script->scriptSource(), &haveSource)) { @@ -1056,54 +1057,10 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) if (!src) return nullptr; - // The source data for functions created by calling the Function - // constructor is only the function's body. This depends on the fact, - // asserted below, that in Function("function f() {}"), the inner - // function's sourceStart points to the '(', not the 'f'. - bool funCon = !fun->isArrow() && - script->sourceStart() == 0 && - script->sourceEnd() == script->scriptSource()->length() && - script->scriptSource()->argumentsNotIncluded(); - - // Functions created with the constructor can't be arrow functions or - // expression closures. - MOZ_ASSERT_IF(funCon, !fun->isArrow()); - MOZ_ASSERT_IF(funCon, !fun->isExprBody()); - MOZ_ASSERT_IF(!funCon && !fun->isArrow(), - src->length() > 0 && src->latin1OrTwoByteChar(0) == '('); - - bool buildBody = funCon; - if (buildBody) { - // This function was created with the Function constructor. We don't - // have source for the arguments, so we have to generate that. Part - // of bug 755821 should be cobbling the arguments passed into the - // Function constructor into the source string. - if (!out.append("(")) - return nullptr; - - // Fish out the argument names. - MOZ_ASSERT(script->numArgs() == fun->nargs()); - - BindingIter bi(script); - for (unsigned i = 0; i < fun->nargs(); i++, bi++) { - MOZ_ASSERT(bi.argumentSlot() == i); - if (i && !out.append(", ")) - return nullptr; - if (i == unsigned(fun->nargs() - 1) && fun->hasRest() && !out.append("...")) - return nullptr; - if (!out.append(bi.name())) - return nullptr; - } - if (!out.append(") {\n")) - return nullptr; - } if (!out.append(src)) return nullptr; - if (buildBody) { - if (!out.append("\n}")) - return nullptr; - } - if (!lambdaParen && funIsMethodOrNonArrowLambda) { + + if (!prettyPrint && funIsMethodOrNonArrowLambda) { if (!out.append(")")) return nullptr; } @@ -1114,8 +1071,6 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) { return nullptr; } - if (!lambdaParen && fun->isLambda() && !fun->isArrow() && !out.append(")")) - return nullptr; } else { MOZ_ASSERT(!fun->isExprBody()); @@ -1657,17 +1612,6 @@ fun_isGenerator(JSContext* cx, unsigned argc, Value* vp) return true; } -/* - * Report "malformed formal parameter" iff no illegal char or similar scanner - * error was already reported. - */ -static bool -OnBadFormal(JSContext* cx) -{ - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_FORMAL); - return false; -} - const JSFunctionSpec js::function_methods[] = { #if JS_HAS_TOSOURCE JS_FN(js_toSource_str, fun_toSource, 0,0), @@ -1681,11 +1625,12 @@ const JSFunctionSpec js::function_methods[] = { JS_FS_END }; +// ES 2017 draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 19.2.1.1.1. static bool FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generatorKind, FunctionAsyncKind asyncKind) { - /* Block this call if security callbacks forbid it. */ + // Block this call if security callbacks forbid it. Rooted global(cx, &args.callee().global()); if (!GlobalObject::isRuntimeCodeGenEnabled(cx, global)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_FUNCTION); @@ -1717,82 +1662,65 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator introducerFilename = maybeScript->scriptSource()->introducerFilename(); CompileOptions options(cx); + // Use line 0 to make the function body starts from line 1. options.setMutedErrors(mutedErrors) - .setFileAndLine(filename, 1) + .setFileAndLine(filename, 0) .setNoScriptRval(false) .setIntroductionInfo(introducerFilename, introductionType, lineno, maybeScript, pcOffset); - Vector paramStr(cx); - RootedString bodyText(cx); + StringBuffer sb(cx); - if (args.length() == 0) { - bodyText = cx->names().empty; - } else { - // Collect the function-argument arguments into one string, separated - // by commas, then make a tokenstream from that string, and scan it to - // get the arguments. We need to throw the full scanner at the - // problem because the argument string may contain comments, newlines, - // destructuring arguments, and similar manner of insanities. ("I have - // a feeling we're not in simple-comma-separated-parameters land any - // more, Toto....") - // - // XXX It'd be better if the parser provided utility methods to parse - // an argument list, and to parse a function body given a parameter - // list. But our parser provides no such pleasant interface now. - unsigned n = args.length() - 1; + if (!sb.append('(')) + return false; - // Convert the parameters-related arguments to strings, and determine - // the length of the string containing the overall parameter list. - mozilla::CheckedInt paramStrLen = 0; + if (args.length() > 1) { RootedString str(cx); + + // Steps 5-6, 9. + unsigned n = args.length() - 1; + for (unsigned i = 0; i < n; i++) { + // Steps 9.a-b, 9.d.i-ii. str = ToString(cx, args[i]); if (!str) return false; - args[i].setString(str); - paramStrLen += str->length(); - } - - // Tack in space for any combining commas. - if (n > 0) - paramStrLen += n - 1; + // Steps 9.b, 9.d.iii. + if (!sb.append(str)) + return false; - // Check for integer and string-size overflow. - if (!paramStrLen.isValid() || paramStrLen.value() > JSString::MAX_LENGTH) { - ReportAllocationOverflow(cx); - return false; + if (i < args.length() - 2) { + // Step 9.d.iii. + if (!sb.append(", ")) + return false; + } } + } - uint32_t paramsLen = paramStrLen.value(); + // Remember the position of ")". + Maybe parameterListEnd = Some(uint32_t(sb.length())); + MOZ_ASSERT(FunctionConstructorMedialSigils[0] == ')'); - // Fill a vector with the comma-joined arguments. Careful! This - // string is *not* null-terminated! - MOZ_ASSERT(paramStr.length() == 0); - if (!paramStr.growBy(paramsLen)) { - ReportOutOfMemory(cx); - return false; - } - - char16_t* cp = paramStr.begin(); - for (unsigned i = 0; i < n; i++) { - JSLinearString* argLinear = args[i].toString()->ensureLinear(cx); - if (!argLinear) - return false; + if (!sb.append(FunctionConstructorMedialSigils)) + return false; - CopyChars(cp, *argLinear); - cp += argLinear->length(); + if (args.length() > 0) { + // Steps 7-8, 10. + RootedString body(cx, ToString(cx, args[args.length() - 1])); + if (!body || !sb.append(body)) + return false; + } - if (i + 1 < n) - *cp++ = ','; - } + if (!sb.append(FunctionConstructorFinalBrace)) + return false; - MOZ_ASSERT(cp == paramStr.end()); + // The parser only accepts two byte strings. + if (!sb.ensureTwoByteChars()) + return false; - bodyText = ToString(cx, args[n]); - if (!bodyText) - return false; - } + RootedString functionText(cx, sb.finishString()); + if (!functionText) + return false; /* * NB: (new Function) is not lexically closed by its caller, it's just an @@ -1802,24 +1730,22 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator */ RootedAtom anonymousAtom(cx, cx->names().anonymous); - // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 - // 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction, step 24. + // Step 24. RootedObject proto(cx); if (!isAsync) { if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; } - // 19.2.1.1.1, step 4.d, use %Generator% as the fallback prototype. + // Step 4.d, use %Generator% as the fallback prototype. // Also use %Generator% for the unwrapped function of async functions. if (!proto && isStarGenerator) { - // Unwrapped function of async function should use GeneratorFunction, - // while wrapped function isn't generator. proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global); if (!proto) return false; } + // Step 25-32 (reordered). RootedObject globalLexical(cx, &global->lexicalEnvironment()); AllocKind allocKind = isAsync ? AllocKind::FUNCTION_EXTENDED : AllocKind::FUNCTION; RootedFunction fun(cx, NewFunctionWithProto(cx, nullptr, 0, @@ -1832,81 +1758,11 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator if (!JSFunction::setTypeForScriptedFunction(cx, fun)) return false; + // Steps 2.a-b, 3.a-b, 4.a-b, 11-23. AutoStableStringChars stableChars(cx); - if (!stableChars.initTwoByte(cx, bodyText)) + if (!stableChars.initTwoByte(cx, functionText)) return false; - bool hasRest = false; - - Rooted formals(cx, PropertyNameVector(cx)); - if (args.length() > 1) { - // Initialize a tokenstream to parse the new function's arguments. No - // StrictModeGetter is needed because this TokenStream won't report any - // strict mode errors. Strict mode errors that might be reported here - // (duplicate argument names, etc.) will be detected when we compile - // the function body. - // - // XXX Bug! We have to parse the body first to determine strictness. - // We have to know strictness to parse arguments correctly, in case - // arguments contains a strict mode violation. And we should be - // using full-fledged arguments parsing here, in order to handle - // destructuring and other exotic syntaxes. - AutoKeepAtoms keepAtoms(cx->perThreadData); - TokenStream ts(cx, options, paramStr.begin(), paramStr.length(), - /* strictModeGetter = */ nullptr); - bool yieldIsValidName = ts.versionNumber() < JSVERSION_1_7 && !isStarGenerator; - - // The argument string may be empty or contain no tokens. - TokenKind tt; - if (!ts.getToken(&tt)) - return false; - if (tt != TOK_EOF) { - while (true) { - // Check that it's a name. - if (hasRest) { - ts.reportError(JSMSG_PARAMETER_AFTER_REST); - return false; - } - - if (tt == TOK_YIELD && yieldIsValidName) - tt = TOK_NAME; - - if (tt != TOK_NAME) { - if (tt == TOK_TRIPLEDOT) { - hasRest = true; - if (!ts.getToken(&tt)) - return false; - if (tt == TOK_YIELD && yieldIsValidName) - tt = TOK_NAME; - if (tt != TOK_NAME) { - ts.reportError(JSMSG_NO_REST_NAME); - return false; - } - } else { - return OnBadFormal(cx); - } - } - - if (!formals.append(ts.currentName())) - return false; - - // Get the next token. Stop on end of stream. Otherwise - // insist on a comma, get another name, and iterate. - if (!ts.getToken(&tt)) - return false; - if (tt == TOK_EOF) - break; - if (tt != TOK_COMMA) - return OnBadFormal(cx); - if (!ts.getToken(&tt)) - return false; - } - } - } - - if (hasRest) - fun->setHasRest(); - mozilla::Range chars = stableChars.twoByteRange(); SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership @@ -1914,11 +1770,13 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator bool ok; SourceBufferHolder srcBuf(chars.begin().get(), chars.length(), ownership); if (isAsync) - ok = frontend::CompileAsyncFunctionBody(cx, &fun, options, formals, srcBuf); + ok = frontend::CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf, parameterListEnd); else if (isStarGenerator) - ok = frontend::CompileStarGeneratorBody(cx, &fun, options, formals, srcBuf); + ok = frontend::CompileStandaloneGenerator(cx, &fun, options, srcBuf, parameterListEnd); else - ok = frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf); + ok = frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, parameterListEnd); + + // Step 33. args.rval().setObject(*fun); return ok; } diff --git a/js/src/jsfun.h b/js/src/jsfun.h index ef4a603d0..88af5c22d 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -28,6 +28,9 @@ static const uint32_t JSSLOT_BOUND_FUNCTION_TARGET = 2; static const uint32_t JSSLOT_BOUND_FUNCTION_THIS = 3; static const uint32_t JSSLOT_BOUND_FUNCTION_ARGS = 4; +static const char FunctionConstructorMedialSigils[] = ") {\n"; +static const char FunctionConstructorFinalBrace[] = "\n}"; + class JSFunction : public js::NativeObject { public: @@ -816,7 +819,7 @@ JSFunction::getExtendedSlot(size_t which) const namespace js { -JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen); +JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPring); template bool diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 10821f26a..b568b4b30 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1689,6 +1689,16 @@ ScriptSource::substringDontDeflate(JSContext* cx, size_t start, size_t stop) return NewStringCopyNDontDeflate(cx, chars, len); } +JSFlatString* +ScriptSource::functionBodyString(JSContext* cx) +{ + MOZ_ASSERT(isFunctionBody()); + + size_t start = parameterListEnd_ + (sizeof(FunctionConstructorMedialSigils) - 1); + size_t stop = length() - (sizeof(FunctionConstructorFinalBrace) - 1); + return substring(cx, start, stop); +} + MOZ_MUST_USE bool ScriptSource::setSource(ExclusiveContext* cx, mozilla::UniquePtr&& source, @@ -1740,10 +1750,9 @@ ScriptSource::setCompressedSource(SharedImmutableString&& raw, size_t uncompress bool ScriptSource::setSourceCopy(ExclusiveContext* cx, SourceBufferHolder& srcBuf, - bool argumentsNotIncluded, SourceCompressionTask* task) + SourceCompressionTask* task) { MOZ_ASSERT(!hasSourceData()); - argumentsNotIncluded_ = argumentsNotIncluded; auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings(); auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&]() { @@ -1940,16 +1949,6 @@ ScriptSource::performXDR(XDRState* xdr) if (!xdr->codeUint32(&compressedLength)) return false; - { - uint8_t argumentsNotIncluded; - if (mode == XDR_ENCODE) - argumentsNotIncluded = argumentsNotIncluded_; - if (!xdr->codeUint8(&argumentsNotIncluded)) - return false; - if (mode == XDR_DECODE) - argumentsNotIncluded_ = argumentsNotIncluded; - } - size_t byteLen = compressedLength ? compressedLength : (len * sizeof(char16_t)); if (mode == XDR_DECODE) { uint8_t* p = xdr->cx()->template pod_malloc(Max(byteLen, 1)); @@ -2074,7 +2073,8 @@ FormatIntroducedFilename(ExclusiveContext* cx, const char* filename, unsigned li } bool -ScriptSource::initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options) +ScriptSource::initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + Maybe parameterListEnd) { MOZ_ASSERT(!filename_); MOZ_ASSERT(!introducerFilename_); @@ -2083,6 +2083,7 @@ ScriptSource::initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions introductionType_ = options.introductionType; setIntroductionOffset(options.introductionOffset); + parameterListEnd_ = parameterListEnd.isSome() ? parameterListEnd.value() : 0; if (options.hasIntroductionInfo) { MOZ_ASSERT(options.introductionType != nullptr); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index bc8bda83d..ffd4b1e2e 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -10,6 +10,7 @@ #define jsscript_h #include "mozilla/Atomics.h" +#include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" #include "mozilla/Variant.h" @@ -399,6 +400,11 @@ class ScriptSource // scripts. uint32_t introductionOffset_; + // If this source is for Function constructor, the position of ")" after + // parameter list in the source. This is used to get function body. + // 0 for other cases. + uint32_t parameterListEnd_; + // If this ScriptSource was generated by a code-introduction mechanism such // as |eval| or |new Function|, the debugger needs access to the "raw" // filename of the top-level script that contains the eval-ing code. To @@ -428,7 +434,6 @@ class ScriptSource // demand. If sourceRetrievable_ and hasSourceData() are false, it is not // possible to get source at all. bool sourceRetrievable_:1; - bool argumentsNotIncluded_:1; bool hasIntroductionOffset_:1; const char16_t* chunkChars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder, @@ -443,10 +448,10 @@ class ScriptSource sourceMapURL_(nullptr), mutedErrors_(false), introductionOffset_(0), + parameterListEnd_(0), introducerFilename_(nullptr), introductionType_(nullptr), sourceRetrievable_(false), - argumentsNotIncluded_(false), hasIntroductionOffset_(false) { } @@ -461,10 +466,10 @@ class ScriptSource if (--refs == 0) js_delete(this); } - bool initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options); + bool initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + mozilla::Maybe parameterListEnd = mozilla::Nothing()); bool setSourceCopy(ExclusiveContext* cx, JS::SourceBufferHolder& srcBuf, - bool argumentsNotIncluded, SourceCompressionTask* tok); void setSourceRetrievable() { sourceRetrievable_ = true; } bool sourceRetrievable() const { return sourceRetrievable_; } @@ -492,11 +497,6 @@ class ScriptSource return data.match(LengthMatcher()); } - bool argumentsNotIncluded() const { - MOZ_ASSERT(hasSourceData()); - return argumentsNotIncluded_; - } - // Return a string containing the chars starting at |begin| and ending at // |begin + len|. const char16_t* chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp, @@ -504,6 +504,12 @@ class ScriptSource JSFlatString* substring(JSContext* cx, size_t start, size_t stop); JSFlatString* substringDontDeflate(JSContext* cx, size_t start, size_t stop); + + bool isFunctionBody() { + return parameterListEnd_ != 0; + } + JSFlatString* functionBodyString(JSContext* cx); + void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ScriptSourceInfo* info) const; @@ -567,6 +573,10 @@ class ScriptSource introductionOffset_ = offset; hasIntroductionOffset_ = true; } + + uint32_t parameterListEnd() const { + return parameterListEnd_; + } }; class ScriptSourceHolder diff --git a/js/src/tests/ecma_6/Function/invalid-parameter-list.js b/js/src/tests/ecma_6/Function/invalid-parameter-list.js new file mode 100644 index 000000000..8aae89ef1 --- /dev/null +++ b/js/src/tests/ecma_6/Function/invalid-parameter-list.js @@ -0,0 +1,27 @@ +// This constructor behaves like `Function` without checking +// if the parameter list end is at the expected position. +// We use this to make sure that the tests we use are otherwise +// syntactically correct. +function DumpFunction(...args) { + let code = "function anonymous("; + code += args.slice(0, -1).join(", "); + code += ") {\n"; + code += args[args.length -1]; + code += "\n}"; + eval(code); +} + +const tests = [ + ["/*", "*/) {"], + ["//", ") {"], + ["a = `", "` ) {"], + [") { var x = function (", "} "], + ["x = function (", "}) {"] +]; + +for (const test of tests) { + DumpFunction(...test); + assertThrowsInstanceOf(() => new Function(...test), SyntaxError); +} + +reportCompare(0, 0, 'ok'); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index b6bc7d62a..4d181545f 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -6890,8 +6890,13 @@ class DebuggerSourceGetTextMatcher bool hasSourceData = ss->hasSourceData(); if (!ss->hasSourceData() && !JSScript::loadSource(cx_, ss, &hasSourceData)) return nullptr; - return hasSourceData ? ss->substring(cx_, 0, ss->length()) - : NewStringCopyZ(cx_, "[no source]"); + if (!hasSourceData) + return NewStringCopyZ(cx_, "[no source]"); + + if (ss->isFunctionBody()) + return ss->functionBodyString(cx_); + + return ss->substring(cx_, 0, ss->length()); } ReturnType match(Handle wasmInstance) { diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 34f5a8c0d..4dbc9b387 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -21,6 +21,7 @@ #include "mozilla/Attributes.h" #include "mozilla/Compression.h" #include "mozilla/MathAlgorithms.h" +#include "mozilla/Maybe.h" #include "jsmath.h" #include "jsprf.h" @@ -8033,21 +8034,6 @@ TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata return true; } -static MOZ_MUST_USE bool -MaybeAppendUTF8Name(JSContext* cx, const char* utf8Chars, MutableHandle names) -{ - if (!utf8Chars) - return true; - - UTF8Chars utf8(utf8Chars, strlen(utf8Chars)); - - JSAtom* atom = AtomizeUTF8Chars(cx, utf8Chars, strlen(utf8Chars)); - if (!atom) - return false; - - return names.append(atom->asPropertyName()); -} - static bool HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& metadata) { @@ -8068,8 +8054,8 @@ HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& me return false; } - uint32_t begin = metadata.srcBodyStart; // starts right after 'use asm' - uint32_t end = metadata.srcEndBeforeCurly(); + uint32_t begin = metadata.srcStart; + uint32_t end = metadata.srcEndAfterCurly(); Rooted src(cx, source->substringDontDeflate(cx, begin, end)); if (!src) return false; @@ -8080,18 +8066,11 @@ HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& me if (!fun) return false; - Rooted formals(cx, PropertyNameVector(cx)); - if (!MaybeAppendUTF8Name(cx, metadata.globalArgumentName.get(), &formals)) - return false; - if (!MaybeAppendUTF8Name(cx, metadata.importArgumentName.get(), &formals)) - return false; - if (!MaybeAppendUTF8Name(cx, metadata.bufferArgumentName.get(), &formals)) - return false; - CompileOptions options(cx); options.setMutedErrors(source->mutedErrors()) .setFile(source->filename()) .setNoScriptRval(false); + options.asmJSOption = AsmJSOption::Disabled; // The exported function inherits an implicit strict context if the module // also inherited it somehow. @@ -8106,8 +8085,8 @@ HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& me SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership : SourceBufferHolder::NoOwnership; - SourceBufferHolder srcBuf(chars, end - begin, ownership); - if (!frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf)) + SourceBufferHolder srcBuf(chars, stableChars.twoByteRange().length(), ownership); + if (!frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, Nothing())) return false; // Call the function we just recompiled. @@ -8849,23 +8828,6 @@ js::IsAsmJSModuleLoadedFromCache(JSContext* cx, unsigned argc, Value* vp) /*****************************************************************************/ // asm.js toString/toSource support -static MOZ_MUST_USE bool -MaybeAppendUTF8Chars(JSContext* cx, const char* sep, const char* utf8Chars, StringBuffer* sb) -{ - if (!utf8Chars) - return true; - - UTF8Chars utf8(utf8Chars, strlen(utf8Chars)); - - size_t length; - UniqueTwoByteChars twoByteChars(UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length).get()); - if (!twoByteChars) - return false; - - return sb->append(sep, strlen(sep)) && - sb->append(twoByteChars.get(), length); -} - JSString* js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda) { @@ -8895,33 +8857,12 @@ js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda if (!out.append("() {\n [sourceless code]\n}")) return nullptr; } else { - // Whether the function has been created with a Function ctor - bool funCtor = begin == 0 && end == source->length() && source->argumentsNotIncluded(); - if (funCtor) { - // Functions created with the function constructor don't have arguments in their source. - if (!out.append("(")) - return nullptr; - - if (!MaybeAppendUTF8Chars(cx, "", metadata.globalArgumentName.get(), &out)) - return nullptr; - if (!MaybeAppendUTF8Chars(cx, ", ", metadata.importArgumentName.get(), &out)) - return nullptr; - if (!MaybeAppendUTF8Chars(cx, ", ", metadata.bufferArgumentName.get(), &out)) - return nullptr; - - if (!out.append(") {\n")) - return nullptr; - } - Rooted src(cx, source->substring(cx, begin, end)); if (!src) return nullptr; if (!out.append(src)) return nullptr; - - if (funCtor && !out.append("\n}")) - return nullptr; } if (addParenToLambda && fun->isLambda() && !out.append(")")) @@ -8959,10 +8900,6 @@ js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) if (!out.append("() {\n [sourceless code]\n}")) return nullptr; } else { - // asm.js functions cannot have been created with a Function constructor - // as they belong within a module. - MOZ_ASSERT(!(begin == 0 && end == source->length() && source->argumentsNotIncluded())); - Rooted src(cx, source->substring(cx, begin, end)); if (!src) return nullptr; -- cgit v1.2.3 From 56bcb6b5af91696e2700b6477db2473b5921bce1 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 17 Mar 2018 10:47:56 +0100 Subject: Handle same-compartment wrappers in TypedArray methods. CallTypedArrayMethodIfWrapped (and the CallNonGeneric machinery throughout the engine) unwraps the `this` argument, but the other arguments are only rewrapped for the target compartment. The pattern being used before this patch to get the length of a TypedArray or possible TypedArray wrapper is: `callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength")` The first `O` is the `this` value and the second is an argument. If `O` is a cross-compartment wrapper, this works fine. The first `O` is unwrapped, revealing the actual TypedArray object; the second `O` is rewrapped for that TypedArray's compartment, producing the same TypedArray. However, if `O` is a same-compartment wrapper, this doesn't work. The first `O` is unwrapped, revealing the actual TypedArray object in the same compartment; rewrapping the other `O` does nothing to it, since it is already an object in the target compartment. Thus TypedArrayLength receives a `this` value that's an unwrapped TypedArray, but an argument that is still a wrapper. The fix is to have CallTypedArrayMethodIfWrapped targets only expect `this` to be an unwrapped TypedArray. --- js/src/builtin/TypedArray.js | 75 ++++++++++------------ .../jit-test/tests/proxy/testWrapWithProtoIter.js | 1 + .../tests/proxy/testWrapWithProtoTypedArray.js | 19 ++++++ 3 files changed, 55 insertions(+), 40 deletions(-) create mode 100644 js/src/jit-test/tests/proxy/testWrapWithProtoIter.js create mode 100644 js/src/jit-test/tests/proxy/testWrapWithProtoTypedArray.js (limited to 'js/src') diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 4d2d6488f..4a3f38365 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -35,6 +35,10 @@ function IsDetachedBuffer(buffer) { return (flags & JS_ARRAYBUFFER_DETACHED_FLAG) !== 0; } +function TypedArrayLengthMethod() { + return TypedArrayLength(this); +} + function GetAttachedArrayBuffer(tarray) { var buffer = ViewedArrayBufferIfReified(tarray); if (IsDetachedBuffer(buffer)) @@ -42,6 +46,10 @@ function GetAttachedArrayBuffer(tarray) { return buffer; } +function GetAttachedArrayBufferMethod() { + return GetAttachedArrayBuffer(this); +} + // A function which ensures that the argument is either a typed array or a // cross-compartment wrapper for a typed array and that the typed array involved // has an attached array buffer. If one of those conditions doesn't hold (wrong @@ -54,10 +62,7 @@ function IsTypedArrayEnsuringArrayBuffer(arg) { return true; } - // This is a bit hacky but gets the job done: the first `arg` is used to - // test for a wrapped typed array, the second as an argument to - // GetAttachedArrayBuffer. - callFunction(CallTypedArrayMethodIfWrapped, arg, arg, "GetAttachedArrayBuffer"); + callFunction(CallTypedArrayMethodIfWrapped, arg, "GetAttachedArrayBufferMethod"); return false; } @@ -98,8 +103,8 @@ function TypedArrayCreateWithLength(constructor, length) { if (isTypedArray) { len = TypedArrayLength(newTypedArray); } else { - len = callFunction(CallTypedArrayMethodIfWrapped, newTypedArray, newTypedArray, - "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, newTypedArray, + "TypedArrayLengthMethod"); } if (len < length) @@ -259,15 +264,14 @@ function TypedArrayEvery(callbackfn/*, thisArg*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Steps 3-5. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 6. if (arguments.length === 0) @@ -348,15 +352,14 @@ function TypedArrayFilter(callbackfn/*, thisArg*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Step 3. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 4. if (arguments.length === 0) @@ -410,15 +413,14 @@ function TypedArrayFind(predicate/*, thisArg*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Steps 3-5. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 6. if (arguments.length === 0) @@ -452,15 +454,14 @@ function TypedArrayFindIndex(predicate/*, thisArg*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Steps 3-5. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 6. if (arguments.length === 0) @@ -492,15 +493,14 @@ function TypedArrayForEach(callbackfn/*, thisArg*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Step 3-4. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 5. if (arguments.length === 0) @@ -686,15 +686,14 @@ function TypedArrayMap(callbackfn/*, thisArg*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Step 3. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 4. if (arguments.length === 0) @@ -730,15 +729,14 @@ function TypedArrayReduce(callbackfn/*, initialValue*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Steps 3-5. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 6. if (arguments.length === 0) @@ -776,15 +774,14 @@ function TypedArrayReduceRight(callbackfn/*, initialValue*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Steps 3-5. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 6. if (arguments.length === 0) @@ -1034,15 +1031,14 @@ function TypedArraySome(callbackfn/*, thisArg*/) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Steps 3-5. var len; if (isTypedArray) len = TypedArrayLength(O); else - len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); // Step 6. if (arguments.length === 0) @@ -1137,7 +1133,7 @@ function TypedArraySort(comparefn) { if (isTypedArray) { buffer = GetAttachedArrayBuffer(obj); } else { - buffer = callFunction(CallTypedArrayMethodIfWrapped, obj, obj, "GetAttachedArrayBuffer"); + buffer = callFunction(CallTypedArrayMethodIfWrapped, obj, "GetAttachedArrayBufferMethod"); } // Step 3. @@ -1145,7 +1141,7 @@ function TypedArraySort(comparefn) { if (isTypedArray) { len = TypedArrayLength(obj); } else { - len = callFunction(CallTypedArrayMethodIfWrapped, obj, obj, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, obj, "TypedArrayLengthMethod"); } if (comparefn === undefined) { @@ -1181,8 +1177,8 @@ function TypedArraySort(comparefn) { if (isTypedArray) { buffer = GetAttachedArrayBuffer(obj); } else { - buffer = callFunction(CallTypedArrayMethodIfWrapped, obj, obj, - "GetAttachedArrayBuffer"); + buffer = callFunction(CallTypedArrayMethodIfWrapped, obj, + "GetAttachedArrayBufferMethod"); } } var bufferDetached; @@ -1217,15 +1213,14 @@ function TypedArrayToLocaleString(locales = undefined, options = undefined) { // We want to make sure that we have an attached buffer, per spec prose. var isTypedArray = IsTypedArrayEnsuringArrayBuffer(array); - // If we got here, `this` is either a typed array or a cross-compartment - // wrapper for one. + // If we got here, `this` is either a typed array or a wrapper for one. // Step 2. var len; if (isTypedArray) len = TypedArrayLength(array); else - len = callFunction(CallTypedArrayMethodIfWrapped, array, array, "TypedArrayLength"); + len = callFunction(CallTypedArrayMethodIfWrapped, array, "TypedArrayLengthMethod"); // Step 4. if (len === 0) diff --git a/js/src/jit-test/tests/proxy/testWrapWithProtoIter.js b/js/src/jit-test/tests/proxy/testWrapWithProtoIter.js new file mode 100644 index 000000000..c6854b206 --- /dev/null +++ b/js/src/jit-test/tests/proxy/testWrapWithProtoIter.js @@ -0,0 +1 @@ +[...wrapWithProto(new Int8Array(), new Int8Array())] diff --git a/js/src/jit-test/tests/proxy/testWrapWithProtoTypedArray.js b/js/src/jit-test/tests/proxy/testWrapWithProtoTypedArray.js new file mode 100644 index 000000000..1b805d30a --- /dev/null +++ b/js/src/jit-test/tests/proxy/testWrapWithProtoTypedArray.js @@ -0,0 +1,19 @@ +let a = wrapWithProto(new Int8Array([1, 3, 5, 6, 9]), new Int8Array()); + +assertEq([...a].toString(), "1,3,5,6,9"); +assertEq(a.every(e => e < 100), true); +assertEq(a.filter(e => e % 2 == 1).toString(), "1,3,5,9"); +assertEq(a.find(e => e > 3), 5); +assertEq(a.findIndex(e => e % 2 == 0), 3); +assertEq(a.map(e => e * 10).toString(), "10,30,50,60,90"); +assertEq(a.reduce((a, b) => a + b, ""), "13569"); +assertEq(a.reduceRight((acc, e) => "(" + e + acc + ")", ""), "(1(3(5(6(9)))))"); +assertEq(a.some(e => e % 2 == 0), true); + +let s = ""; +assertEq(a.forEach(e => s += e), undefined); +assertEq(s, "13569"); + +a.sort((a, b) => b - a); +assertEq(a.toString(), "9,6,5,3,1"); + -- cgit v1.2.3 From 0a815a6f1117caa829937b5ad8e306481a9f896d Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 17 Mar 2018 16:56:31 +0100 Subject: Use ordinary objects for Error prototypes --- js/src/jsapi.h | 3 +- js/src/jsexn.cpp | 225 ++++++++++++++++++++++++++++++----------------- js/src/vm/ErrorObject.h | 7 +- js/src/vm/GlobalObject.h | 17 +++- 4 files changed, 161 insertions(+), 91 deletions(-) (limited to 'js/src') diff --git a/js/src/jsapi.h b/js/src/jsapi.h index a93852fa5..332ce8562 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -650,7 +650,8 @@ typedef enum JSExnType { JSEXN_DEBUGGEEWOULDRUN, JSEXN_WASMCOMPILEERROR, JSEXN_WASMRUNTIMEERROR, - JSEXN_WARN, + JSEXN_ERROR_LIMIT, + JSEXN_WARN = JSEXN_ERROR_LIMIT, JSEXN_LIMIT } JSExnType; diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index d17c6f8b7..d4dce39b0 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -48,18 +48,35 @@ using mozilla::PodArrayZero; static void exn_finalize(FreeOp* fop, JSObject* obj); -bool -Error(JSContext* cx, unsigned argc, Value* vp); - static bool exn_toSource(JSContext* cx, unsigned argc, Value* vp); -static const JSPropertySpec exception_properties[] = { - JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0), - JS_PS_END +#define IMPLEMENT_ERROR_PROTO_CLASS(name) \ + { \ + js_Object_str, \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \ + JS_NULL_CLASS_OPS, \ + &ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \ + } + +const Class +ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = { + IMPLEMENT_ERROR_PROTO_CLASS(Error), + + IMPLEMENT_ERROR_PROTO_CLASS(InternalError), + IMPLEMENT_ERROR_PROTO_CLASS(EvalError), + IMPLEMENT_ERROR_PROTO_CLASS(RangeError), + IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError), + IMPLEMENT_ERROR_PROTO_CLASS(SyntaxError), + IMPLEMENT_ERROR_PROTO_CLASS(TypeError), + IMPLEMENT_ERROR_PROTO_CLASS(URIError), + + IMPLEMENT_ERROR_PROTO_CLASS(DebuggeeWouldRun), + IMPLEMENT_ERROR_PROTO_CLASS(CompileError), + IMPLEMENT_ERROR_PROTO_CLASS(RuntimeError) }; -static const JSFunctionSpec exception_methods[] = { +static const JSFunctionSpec error_methods[] = { #if JS_HAS_TOSOURCE JS_FN(js_toSource_str, exn_toSource, 0, 0), #endif @@ -67,6 +84,92 @@ static const JSFunctionSpec exception_methods[] = { JS_FS_END }; +static const JSPropertySpec error_properties[] = { + JS_STRING_PS("message", "", 0), + JS_STRING_PS("name", "Error", 0), + // Only Error.prototype has .stack! + JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0), + JS_PS_END +}; + +#define IMPLEMENT_ERROR_PROPERTIES(name) \ + { \ + JS_STRING_PS("message", "", 0), \ + JS_STRING_PS("name", #name, 0), \ + JS_PS_END \ + } + +static const JSPropertySpec other_error_properties[JSEXN_ERROR_LIMIT - 1][3] = { + IMPLEMENT_ERROR_PROPERTIES(InternalError), + IMPLEMENT_ERROR_PROPERTIES(EvalError), + IMPLEMENT_ERROR_PROPERTIES(RangeError), + IMPLEMENT_ERROR_PROPERTIES(ReferenceError), + IMPLEMENT_ERROR_PROPERTIES(SyntaxError), + IMPLEMENT_ERROR_PROPERTIES(TypeError), + IMPLEMENT_ERROR_PROPERTIES(URIError), + IMPLEMENT_ERROR_PROPERTIES(DebuggeeWouldRun), + IMPLEMENT_ERROR_PROPERTIES(CompileError), + IMPLEMENT_ERROR_PROPERTIES(RuntimeError) +}; + +#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \ + { \ + ErrorObject::createConstructor, \ + ErrorObject::createProto, \ + nullptr, \ + nullptr, \ + nullptr, \ + other_error_properties[JSProto_##name - JSProto_Error - 1], \ + nullptr, \ + JSProto_Error \ + } + +#define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \ + { \ + ErrorObject::createConstructor, \ + ErrorObject::createProto, \ + nullptr, \ + nullptr, \ + nullptr, \ + other_error_properties[JSProto_##name - JSProto_Error - 1], \ + nullptr, \ + JSProto_Error | ClassSpec::DontDefineConstructor \ + } + +const ClassSpec +ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = { + { + ErrorObject::createConstructor, + ErrorObject::createProto, + nullptr, + nullptr, + error_methods, + error_properties + }, + + IMPLEMENT_NATIVE_ERROR_SPEC(InternalError), + IMPLEMENT_NATIVE_ERROR_SPEC(EvalError), + IMPLEMENT_NATIVE_ERROR_SPEC(RangeError), + IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError), + IMPLEMENT_NATIVE_ERROR_SPEC(SyntaxError), + IMPLEMENT_NATIVE_ERROR_SPEC(TypeError), + IMPLEMENT_NATIVE_ERROR_SPEC(URIError), + + IMPLEMENT_NONGLOBAL_ERROR_SPEC(DebuggeeWouldRun), + IMPLEMENT_NONGLOBAL_ERROR_SPEC(CompileError), + IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError) +}; + +#define IMPLEMENT_ERROR_CLASS(name) \ + { \ + js_Error_str, /* yes, really */ \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \ + JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS) | \ + JSCLASS_BACKGROUND_FINALIZE, \ + &ErrorObjectClassOps, \ + &ErrorObject::classSpecs[JSProto_##name - JSProto_Error ] \ + } + static const ClassOps ErrorObjectClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ @@ -82,67 +185,20 @@ static const ClassOps ErrorObjectClassOps = { nullptr, /* trace */ }; -#define IMPLEMENT_ERROR_CLASS(name, classSpecPtr) \ - { \ - js_Error_str, /* yes, really */ \ - JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \ - JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS) | \ - JSCLASS_BACKGROUND_FINALIZE, \ - &ErrorObjectClassOps, \ - classSpecPtr \ - } - -const ClassSpec -ErrorObject::errorClassSpec_ = { - ErrorObject::createConstructor, - ErrorObject::createProto, - nullptr, - nullptr, - exception_methods, - exception_properties, - nullptr, - 0 -}; - -const ClassSpec -ErrorObject::subErrorClassSpec_ = { - ErrorObject::createConstructor, - ErrorObject::createProto, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - JSProto_Error -}; - -const ClassSpec -ErrorObject::nonGlobalErrorClassSpec_ = { - ErrorObject::createConstructor, - ErrorObject::createProto, - nullptr, - nullptr, - exception_methods, - exception_properties, - nullptr, - JSProto_Error | ClassSpec::DontDefineConstructor -}; - const Class -ErrorObject::classes[JSEXN_LIMIT] = { - IMPLEMENT_ERROR_CLASS(Error, &ErrorObject::errorClassSpec_), - IMPLEMENT_ERROR_CLASS(InternalError, &ErrorObject::subErrorClassSpec_), - IMPLEMENT_ERROR_CLASS(EvalError, &ErrorObject::subErrorClassSpec_), - IMPLEMENT_ERROR_CLASS(RangeError, &ErrorObject::subErrorClassSpec_), - IMPLEMENT_ERROR_CLASS(ReferenceError, &ErrorObject::subErrorClassSpec_), - IMPLEMENT_ERROR_CLASS(SyntaxError, &ErrorObject::subErrorClassSpec_), - IMPLEMENT_ERROR_CLASS(TypeError, &ErrorObject::subErrorClassSpec_), - IMPLEMENT_ERROR_CLASS(URIError, &ErrorObject::subErrorClassSpec_), - +ErrorObject::classes[JSEXN_ERROR_LIMIT] = { + IMPLEMENT_ERROR_CLASS(Error), + IMPLEMENT_ERROR_CLASS(InternalError), + IMPLEMENT_ERROR_CLASS(EvalError), + IMPLEMENT_ERROR_CLASS(RangeError), + IMPLEMENT_ERROR_CLASS(ReferenceError), + IMPLEMENT_ERROR_CLASS(SyntaxError), + IMPLEMENT_ERROR_CLASS(TypeError), + IMPLEMENT_ERROR_CLASS(URIError), // These Error subclasses are not accessible via the global object: - IMPLEMENT_ERROR_CLASS(DebuggeeWouldRun, &ErrorObject::nonGlobalErrorClassSpec_), - IMPLEMENT_ERROR_CLASS(CompileError, &ErrorObject::nonGlobalErrorClassSpec_), - IMPLEMENT_ERROR_CLASS(RuntimeError, &ErrorObject::nonGlobalErrorClassSpec_) + IMPLEMENT_ERROR_CLASS(DebuggeeWouldRun), + IMPLEMENT_ERROR_CLASS(CompileError), + IMPLEMENT_ERROR_CLASS(RuntimeError) }; JSErrorReport* @@ -454,35 +510,40 @@ exn_toSource(JSContext* cx, unsigned argc, Value* vp) /* static */ JSObject* ErrorObject::createProto(JSContext* cx, JSProtoKey key) { - RootedObject errorProto(cx, GenericCreatePrototype(cx, key)); - if (!errorProto) - return nullptr; - - Rooted err(cx, &errorProto->as()); - RootedString emptyStr(cx, cx->names().empty); JSExnType type = ExnTypeFromProtoKey(key); - if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, nullptr, 0, 0, emptyStr)) - return nullptr; - // The various prototypes also have .name in addition to the normal error - // instance properties. - RootedPropertyName name(cx, ClassName(key, cx)); - RootedValue nameValue(cx, StringValue(name)); - if (!DefineProperty(cx, err, cx->names().name, nameValue, nullptr, nullptr, 0)) + if (type == JSEXN_ERR) + return cx->global()->createBlankPrototype(cx, &ErrorObject::protoClasses[JSEXN_ERR]); + + RootedObject protoProto(cx, GlobalObject::getOrCreateErrorPrototype(cx, cx->global())); + if (!protoProto) return nullptr; - return errorProto; + return cx->global()->createBlankPrototypeInheriting(cx, &ErrorObject::protoClasses[type], + protoProto); } /* static */ JSObject* ErrorObject::createConstructor(JSContext* cx, JSProtoKey key) { + JSExnType type = ExnTypeFromProtoKey(key); RootedObject ctor(cx); - ctor = GenericCreateConstructor(cx, key); + + if (type == JSEXN_ERR) { + ctor = GenericCreateConstructor(cx, key); + } else { + RootedFunction proto(cx, GlobalObject::getOrCreateErrorConstructor(cx, cx->global())); + if (!proto) + return nullptr; + + ctor = NewFunctionWithProto(cx, Error, 1, JSFunction::NATIVE_CTOR, nullptr, + ClassName(key, cx), proto, gc::AllocKind::FUNCTION_EXTENDED); + } + if (!ctor) return nullptr; - ctor->as().setExtendedSlot(0, Int32Value(ExnTypeFromProtoKey(key))); + ctor->as().setExtendedSlot(0, Int32Value(type)); return ctor; } diff --git a/js/src/vm/ErrorObject.h b/js/src/vm/ErrorObject.h index 32a691bb4..cfd804e2e 100644 --- a/js/src/vm/ErrorObject.h +++ b/js/src/vm/ErrorObject.h @@ -38,9 +38,8 @@ class ErrorObject : public NativeObject ScopedJSFreePtr* errorReport, HandleString fileName, HandleObject stack, uint32_t lineNumber, uint32_t columnNumber, HandleString message); - static const ClassSpec errorClassSpec_; - static const ClassSpec subErrorClassSpec_; - static const ClassSpec nonGlobalErrorClassSpec_; + static const ClassSpec classSpecs[JSEXN_ERROR_LIMIT]; + static const Class protoClasses[JSEXN_ERROR_LIMIT]; protected: static const uint32_t EXNTYPE_SLOT = 0; @@ -54,7 +53,7 @@ class ErrorObject : public NativeObject static const uint32_t RESERVED_SLOTS = MESSAGE_SLOT + 1; public: - static const Class classes[JSEXN_LIMIT]; + static const Class classes[JSEXN_ERROR_LIMIT]; static const Class * classForType(JSExnType type) { MOZ_ASSERT(type < JSEXN_WARN); diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 05984bc5f..3534ef2f6 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -427,6 +427,18 @@ class GlobalObject : public NativeObject return &global->getPrototype(key).toObject(); } + static JSFunction* + getOrCreateErrorConstructor(JSContext* cx, Handle global) { + if (!ensureConstructor(cx, global, JSProto_Error)) + return nullptr; + return &global->getConstructor(JSProto_Error).toObject().as(); + } + + static JSObject* + getOrCreateErrorPrototype(JSContext* cx, Handle global) { + return getOrCreateCustomErrorPrototype(cx, global, JSEXN_ERR); + } + static NativeObject* getOrCreateSetPrototype(JSContext* cx, Handle global) { if (!ensureConstructor(cx, global, JSProto_Set)) return nullptr; @@ -1003,10 +1015,7 @@ GenericCreatePrototype(JSContext* cx, JSProtoKey key) inline JSProtoKey StandardProtoKeyOrNull(const JSObject* obj) { - JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass()); - if (key == JSProto_Error) - return GetExceptionProtoKey(obj->as().type()); - return key; + return JSCLASS_CACHED_PROTO_KEY(obj->getClass()); } JSObject* -- cgit v1.2.3 From 566f1ff8e70f67181fc933a016011f062749170f Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 17 Mar 2018 16:58:26 +0100 Subject: Handle the now ordinary error prototype object in stack --- js/src/jsexn.h | 9 ++++++- js/src/vm/ErrorObject.cpp | 69 ++++++++++++++++++++++++++--------------------- js/src/vm/ErrorObject.h | 1 + 3 files changed, 48 insertions(+), 31 deletions(-) (limited to 'js/src') diff --git a/js/src/jsexn.h b/js/src/jsexn.h index a63c70909..ae6335209 100644 --- a/js/src/jsexn.h +++ b/js/src/jsexn.h @@ -85,10 +85,17 @@ ExnTypeFromProtoKey(JSProtoKey key) { JSExnType type = static_cast(key - JSProto_Error); MOZ_ASSERT(type >= JSEXN_ERR); - MOZ_ASSERT(type < JSEXN_WARN); + MOZ_ASSERT(type < JSEXN_ERROR_LIMIT); return type; } +static inline bool +IsErrorProtoKey(JSProtoKey key) +{ + JSExnType type = static_cast(key - JSProto_Error); + return type >= JSEXN_ERR && type < JSEXN_ERROR_LIMIT; +} + class AutoClearPendingException { JSContext* cx; diff --git a/js/src/vm/ErrorObject.cpp b/js/src/vm/ErrorObject.cpp index 47b61b57b..d8d29830b 100644 --- a/js/src/vm/ErrorObject.cpp +++ b/js/src/vm/ErrorObject.cpp @@ -164,29 +164,25 @@ js::ErrorObject::getOrCreateErrorReport(JSContext* cx) } static bool -ErrorObject_checkAndUnwrapThis(JSContext* cx, CallArgs& args, const char* fnName, - MutableHandle error) +FindErrorInstanceOrPrototype(JSContext* cx, HandleObject obj, MutableHandleObject result) { - const Value& thisValue = args.thisv(); - - if (!thisValue.isObject()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, - InformalValueTypeName(thisValue)); - return false; - } - - // Walk up the prototype chain until we find the first ErrorObject that has - // the slots we need. This allows us to support the poor-man's subclassing - // of error: Object.create(Error.prototype). - - RootedObject target(cx, CheckedUnwrap(&thisValue.toObject())); + // Walk up the prototype chain until we find an error object instance or + // prototype object. This allows code like: + // Object.create(Error.prototype).stack + // or + // function NYI() { } + // NYI.prototype = new Error; + // (new NYI).stack + // to continue returning stacks that are useless, but at least don't throw. + + RootedObject target(cx, CheckedUnwrap(obj)); if (!target) { JS_ReportErrorASCII(cx, "Permission denied to access object"); return false; } RootedObject proto(cx); - while (!target->is()) { + while (!IsErrorProtoKey(StandardProtoKeyOrNull(target))) { if (!GetPrototype(cx, target, &proto)) return false; @@ -194,7 +190,7 @@ ErrorObject_checkAndUnwrapThis(JSContext* cx, CallArgs& args, const char* fnName // We walked the whole prototype chain and did not find an Error // object. JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, - js_Error_str, fnName, thisValue.toObject().getClass()->name); + js_Error_str, "(get stack)", obj->getClass()->name); return false; } @@ -205,19 +201,40 @@ ErrorObject_checkAndUnwrapThis(JSContext* cx, CallArgs& args, const char* fnName } } - error.set(&target->as()); + result.set(target); return true; } + +static MOZ_ALWAYS_INLINE bool +IsObject(HandleValue v) +{ + return v.isObject(); +} + /* static */ bool js::ErrorObject::getStack(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - Rooted error(cx); - if (!ErrorObject_checkAndUnwrapThis(cx, args, "(get stack)", &error)) + // We accept any object here, because of poor-man's subclassing of Error. + return CallNonGenericMethod(cx, args); +} + +/* static */ bool +js::ErrorObject::getStack_impl(JSContext* cx, const CallArgs& args) +{ + RootedObject thisObj(cx, &args.thisv().toObject()); + + RootedObject obj(cx); + if (!FindErrorInstanceOrPrototype(cx, thisObj, &obj)) return false; - RootedObject savedFrameObj(cx, error->stack()); + if (!obj->is()) { + args.rval().setString(cx->runtime()->emptyString); + return true; + } + + RootedObject savedFrameObj(cx, obj->as().stack()); RootedString stackString(cx); if (!BuildStackString(cx, savedFrameObj, &stackString)) return false; @@ -245,12 +262,6 @@ js::ErrorObject::getStack(JSContext* cx, unsigned argc, Value* vp) return true; } -static MOZ_ALWAYS_INLINE bool -IsObject(HandleValue v) -{ - return v.isObject(); -} - /* static */ bool js::ErrorObject::setStack(JSContext* cx, unsigned argc, Value* vp) { @@ -262,9 +273,7 @@ js::ErrorObject::setStack(JSContext* cx, unsigned argc, Value* vp) /* static */ bool js::ErrorObject::setStack_impl(JSContext* cx, const CallArgs& args) { - const Value& thisValue = args.thisv(); - MOZ_ASSERT(thisValue.isObject()); - RootedObject thisObj(cx, &thisValue.toObject()); + RootedObject thisObj(cx, &args.thisv().toObject()); if (!args.requireAtLeast(cx, "(set stack)", 1)) return false; diff --git a/js/src/vm/ErrorObject.h b/js/src/vm/ErrorObject.h index cfd804e2e..0c2d00610 100644 --- a/js/src/vm/ErrorObject.h +++ b/js/src/vm/ErrorObject.h @@ -106,6 +106,7 @@ class ErrorObject : public NativeObject // Getter and setter for the Error.prototype.stack accessor. static bool getStack(JSContext* cx, unsigned argc, Value* vp); + static bool getStack_impl(JSContext* cx, const CallArgs& args); static bool setStack(JSContext* cx, unsigned argc, Value* vp); static bool setStack_impl(JSContext* cx, const CallArgs& args); }; -- cgit v1.2.3 From c00faa3d194eb0d2154c314b4220105c0d990c5d Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 17 Mar 2018 17:00:25 +0100 Subject: Add TI for error properties assigned by the initial shape --- js/src/vm/ObjectGroup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index 7be697fb6..248abb73a 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -6,6 +6,7 @@ #include "vm/ObjectGroup.h" +#include "jsexn.h" #include "jshashutil.h" #include "jsobj.h" @@ -578,11 +579,10 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp, AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type()); } else if (clasp == &StringObject::class_) { AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type()); - } else if (ErrorObject::isErrorClass((clasp))) { + } else if (IsErrorProtoKey(StandardProtoKeyOrNull(obj))) { AddTypePropertyId(cx, group, nullptr, NameToId(names.fileName), TypeSet::StringType()); AddTypePropertyId(cx, group, nullptr, NameToId(names.lineNumber), TypeSet::Int32Type()); AddTypePropertyId(cx, group, nullptr, NameToId(names.columnNumber), TypeSet::Int32Type()); - AddTypePropertyId(cx, group, nullptr, NameToId(names.stack), TypeSet::StringType()); } return group; -- cgit v1.2.3 From 9ecabc46eb6adb35610698dbbea25a0bb580cc9e Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 17 Mar 2018 17:01:42 +0100 Subject: Tests --- js/src/tests/ecma_6/Error/constructor-proto.js | 17 +++++++++++++++++ js/src/tests/ecma_6/Error/prototype.js | 18 ++++++++++++++++++ js/src/tests/ecma_6/Error/shell.js | 0 3 files changed, 35 insertions(+) create mode 100644 js/src/tests/ecma_6/Error/constructor-proto.js create mode 100644 js/src/tests/ecma_6/Error/prototype.js create mode 100644 js/src/tests/ecma_6/Error/shell.js (limited to 'js/src') diff --git a/js/src/tests/ecma_6/Error/constructor-proto.js b/js/src/tests/ecma_6/Error/constructor-proto.js new file mode 100644 index 000000000..4ddc6025e --- /dev/null +++ b/js/src/tests/ecma_6/Error/constructor-proto.js @@ -0,0 +1,17 @@ +const nativeErrors = [ + InternalError, + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError, + URIError +]; + +assertEq(Reflect.getPrototypeOf(Error), Function.prototype) + +for (const error of nativeErrors) + assertEq(Reflect.getPrototypeOf(error), Error); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Error/prototype.js b/js/src/tests/ecma_6/Error/prototype.js new file mode 100644 index 000000000..b22a8e084 --- /dev/null +++ b/js/src/tests/ecma_6/Error/prototype.js @@ -0,0 +1,18 @@ +const nativeErrors = [ + InternalError, + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError, + URIError +]; + +assertEq(Reflect.getPrototypeOf(Error.prototype), Object.prototype) + +for (const error of nativeErrors) { + assertEq(Reflect.getPrototypeOf(error.prototype), Error.prototype); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Error/shell.js b/js/src/tests/ecma_6/Error/shell.js new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From b794d24326f48db217127185a98b9ead91977e21 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 17 Mar 2018 17:03:00 +0100 Subject: Bug 1319952: Assertion failure: isNurseryAllocAllowed(), at js/src/gc/Allocator.cpp:79 --- js/src/jsexn.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index d4dce39b0..9a8e364ed 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -537,7 +537,8 @@ ErrorObject::createConstructor(JSContext* cx, JSProtoKey key) return nullptr; ctor = NewFunctionWithProto(cx, Error, 1, JSFunction::NATIVE_CTOR, nullptr, - ClassName(key, cx), proto, gc::AllocKind::FUNCTION_EXTENDED); + ClassName(key, cx), proto, gc::AllocKind::FUNCTION_EXTENDED, + SingletonObject); } if (!ctor) -- cgit v1.2.3 From 9472b0a60305dbf386ba1cf57323c1daeaede889 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 17 Mar 2018 17:24:28 +0100 Subject: Bug 1320198: Land additional Error prototype test --- js/src/tests/ecma_6/Error/prototype-properties.js | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 js/src/tests/ecma_6/Error/prototype-properties.js (limited to 'js/src') diff --git a/js/src/tests/ecma_6/Error/prototype-properties.js b/js/src/tests/ecma_6/Error/prototype-properties.js new file mode 100644 index 000000000..c66caf2bc --- /dev/null +++ b/js/src/tests/ecma_6/Error/prototype-properties.js @@ -0,0 +1,24 @@ +const nativeErrors = [ + InternalError, + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError, + URIError +]; + + +assertEq(Reflect.ownKeys(Error.prototype).toString(), "toSource,toString,message,name,stack,constructor"); +assertEq(Error.prototype.name, "Error"); +assertEq(Error.prototype.message, ""); + +for (const error of nativeErrors) { + assertEq(Reflect.ownKeys(error.prototype).toString(), "message,name,constructor"); + assertEq(error.prototype.name, error.name); + assertEq(error.prototype.message, ""); + assertEq(error.prototype.constructor, error); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); -- cgit v1.2.3 From 77e607426f03524bdaf06bfc6799f5e9273051b9 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 17 Mar 2018 18:07:21 +0100 Subject: Add TI for error properties assigned by the initial shape (follow up) --- js/src/vm/ObjectGroup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index 248abb73a..d6a8fcaa4 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -579,7 +579,7 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp, AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type()); } else if (clasp == &StringObject::class_) { AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type()); - } else if (IsErrorProtoKey(StandardProtoKeyOrNull(obj))) { + } else if (ErrorObject::isErrorClass(clasp)) { AddTypePropertyId(cx, group, nullptr, NameToId(names.fileName), TypeSet::StringType()); AddTypePropertyId(cx, group, nullptr, NameToId(names.lineNumber), TypeSet::Int32Type()); AddTypePropertyId(cx, group, nullptr, NameToId(names.columnNumber), TypeSet::Int32Type()); -- cgit v1.2.3 From 726d2c3093d1a386edbff106070456cf866c0a6a Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 18 Mar 2018 09:53:57 +0100 Subject: Remove strict arguments poison pill for "caller" property per ES2017 --- js/src/tests/ecma_5/Function/arguments-caller-callee.js | 12 +++++------- .../tests/ecma_5/Function/arguments-property-attributes.js | 8 ++------ js/src/tests/jstests.list | 10 ++++++++++ js/src/vm/ArgumentsObject.cpp | 6 +----- 4 files changed, 18 insertions(+), 18 deletions(-) (limited to 'js/src') diff --git a/js/src/tests/ecma_5/Function/arguments-caller-callee.js b/js/src/tests/ecma_5/Function/arguments-caller-callee.js index 3eda27b49..57efd9eb9 100644 --- a/js/src/tests/ecma_5/Function/arguments-caller-callee.js +++ b/js/src/tests/ecma_5/Function/arguments-caller-callee.js @@ -5,7 +5,8 @@ var gTestfile = 'arguments-caller-callee.js'; var BUGNUMBER = 514563; -var summary = "arguments.caller and arguments.callee are poison pills in ES5"; +var summary = "arguments.caller and arguments.callee are poison pills in ES5, " + + "later changed in ES2017 to only poison pill arguments.callee."; print(BUGNUMBER + ": " + summary); @@ -31,7 +32,7 @@ function expectTypeError(fun) } function bar() { "use strict"; return arguments; } -expectTypeError(function barCaller() { bar().caller; }); +assertEq(bar().caller, undefined); // No error when accessing arguments.caller in ES2017+ expectTypeError(function barCallee() { bar().callee; }); function baz() { return arguments; } @@ -41,15 +42,12 @@ assertEq(baz().callee, baz); // accessor identity function strictMode() { "use strict"; return arguments; } -var canonicalTTE = Object.getOwnPropertyDescriptor(strictMode(), "caller").get; +var canonicalTTE = Object.getOwnPropertyDescriptor(strictMode(), "callee").get; var args = strictMode(); var argsCaller = Object.getOwnPropertyDescriptor(args, "caller"); -assertEq("get" in argsCaller, true); -assertEq("set" in argsCaller, true); -assertEq(argsCaller.get, canonicalTTE); -assertEq(argsCaller.set, canonicalTTE); +assertEq(argsCaller, undefined); var argsCallee = Object.getOwnPropertyDescriptor(args, "callee"); assertEq("get" in argsCallee, true); diff --git a/js/src/tests/ecma_5/Function/arguments-property-attributes.js b/js/src/tests/ecma_5/Function/arguments-property-attributes.js index 994b97ca4..f50c1e6da 100644 --- a/js/src/tests/ecma_5/Function/arguments-property-attributes.js +++ b/js/src/tests/ecma_5/Function/arguments-property-attributes.js @@ -56,7 +56,7 @@ var sa = strictArgs(0, 1); var strictArgProps = Object.getOwnPropertyNames(sa).sort(); assertEq(strictArgProps.indexOf("callee") >= 0, true); -assertEq(strictArgProps.indexOf("caller") >= 0, true); +assertEq(strictArgProps.indexOf("caller") >= 0, false); assertEq(strictArgProps.indexOf("0") >= 0, true); assertEq(strictArgProps.indexOf("1") >= 0, true); assertEq(strictArgProps.indexOf("length") >= 0, true); @@ -69,11 +69,7 @@ assertEq(strictCalleeDesc.enumerable, false); assertEq(strictCalleeDesc.configurable, false); var strictCallerDesc = Object.getOwnPropertyDescriptor(sa, "caller"); -assertEq(typeof strictCallerDesc.get, "function"); -assertEq(typeof strictCallerDesc.set, "function"); -assertEq(strictCallerDesc.get, strictCallerDesc.set); -assertEq(strictCallerDesc.enumerable, false); -assertEq(strictCallerDesc.configurable, false); +assertEq(strictCallerDesc, undefined); var strictZeroDesc = Object.getOwnPropertyDescriptor(sa, "0"); assertEq(strictZeroDesc.value, 0); diff --git a/js/src/tests/jstests.list b/js/src/tests/jstests.list index a0f2f08bd..1e23a3da3 100644 --- a/js/src/tests/jstests.list +++ b/js/src/tests/jstests.list @@ -46,6 +46,16 @@ skip script test262/ch09/9.3/9.3.1/S9.3.1_A2.js skip script test262/ch09/9.3/9.3.1/S9.3.1_A3_T2.js skip script test262/ch09/9.3/9.3.1/S9.3.1_A3_T1.js +# ES2017 removed the strict arguments poison pill for the "caller" property +# (bug 1324208). +skip script test262/ch13/13.2/S13.2.3_A1.js +skip script test262/ch10/10.6/10.6-14-1-s.js +skip script test262/ch10/10.6/10.6-13-b-3-s.js +skip script test262/ch10/10.6/10.6-14-b-1-s.js +skip script test262/ch10/10.6/10.6-14-b-4-s.js +skip script test262/ch10/10.6/10.6-13-b-1-s.js +skip script test262/ch10/10.6/10.6-13-b-2-s.js + ####################################################################### # Tests disabled due to jstest limitations wrt imported test262 tests # ####################################################################### diff --git a/js/src/vm/ArgumentsObject.cpp b/js/src/vm/ArgumentsObject.cpp index d01121ef0..717aa1050 100644 --- a/js/src/vm/ArgumentsObject.cpp +++ b/js/src/vm/ArgumentsObject.cpp @@ -676,7 +676,7 @@ UnmappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj, HandleId i if (argsobj->hasOverriddenLength()) return true; } else { - if (!JSID_IS_ATOM(id, cx->names().callee) && !JSID_IS_ATOM(id, cx->names().caller)) + if (!JSID_IS_ATOM(id, cx->names().callee)) return true; attrs = JSPROP_PERMANENT | JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED; @@ -709,10 +709,6 @@ UnmappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) if (!HasProperty(cx, argsobj, id, &found)) return false; - id = NameToId(cx->names().caller); - if (!HasProperty(cx, argsobj, id, &found)) - return false; - id = SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator); if (!HasProperty(cx, argsobj, id, &found)) return false; -- cgit v1.2.3 From 427e7346629c76a1676a3f92320c3d2575d01b85 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 18 Mar 2018 14:55:49 +0100 Subject: Correctly tokenize valid JS names when using Unicode mathematical alphanumeric symbols as variable name Issue https://github.com/MoonchildProductions/Pale-Moon/issues/1647 --- js/src/frontend/TokenStream.cpp | 149 +++- js/src/frontend/TokenStream.h | 1 + .../identifiers-with-extended-unicode-escape.js | 18 +- js/src/vm/Unicode.cpp | 879 +++++++++++++++++++++ js/src/vm/Unicode.h | 28 +- js/src/vm/UnicodeNonBMP.h | 10 + js/src/vm/make_unicode.py | 111 ++- 7 files changed, 1159 insertions(+), 37 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index c166ed414..179a7c244 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -118,13 +118,57 @@ IsIdentifier(const CharT* chars, size_t length) return true; } +static uint32_t +GetSingleCodePoint(const char16_t** p, const char16_t* end) +{ + uint32_t codePoint; + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(**p)) && *p + 1 < end) { + char16_t lead = **p; + char16_t maybeTrail = *(*p + 1); + if (unicode::IsTrailSurrogate(maybeTrail)) { + *p += 2; + return unicode::UTF16Decode(lead, maybeTrail); + } + } + + codePoint = **p; + (*p)++; + return codePoint; +} + +static bool +IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) +{ + if (IsIdentifier(chars, length)) + return true; + + if (length == 0) + return false; + + const char16_t* p = chars; + const char16_t* end = chars + length; + uint32_t codePoint; + + codePoint = GetSingleCodePoint(&p, end); + if (!unicode::IsIdentifierStart(codePoint)) + return false; + + while (p < end) { + codePoint = GetSingleCodePoint(&p, end); + if (!unicode::IsIdentifierPart(codePoint)) + return false; + } + + return true; +} + bool frontend::IsIdentifier(JSLinearString* str) { JS::AutoCheckCannotGC nogc; return str->hasLatin1Chars() ? ::IsIdentifier(str->latin1Chars(nogc), str->length()) - : ::IsIdentifier(str->twoByteChars(nogc), str->length()); + : ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); } bool @@ -992,6 +1036,21 @@ IsTokenSane(Token* tp) } #endif +bool +TokenStream::matchTrailForLeadSurrogate(char16_t lead, char16_t* trail, uint32_t* codePoint) +{ + int32_t maybeTrail = getCharIgnoreEOL(); + if (!unicode::IsTrailSurrogate(maybeTrail)) { + ungetCharIgnoreEOL(maybeTrail); + return false; + } + + if (trail) + *trail = maybeTrail; + *codePoint = unicode::UTF16Decode(lead, maybeTrail); + return true; +} + bool TokenStream::putIdentInTokenbuf(const char16_t* identStart) { @@ -1003,11 +1062,39 @@ TokenStream::putIdentInTokenbuf(const char16_t* identStart) tokenbuf.clear(); for (;;) { c = getCharIgnoreEOL(); + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + char16_t trail; + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, &trail, &codePoint)) { + if (!unicode::IsIdentifierPart(codePoint)) + break; + + if (!tokenbuf.append(c) || !tokenbuf.append(trail)) { + userbuf.setAddressOfNextRawChar(tmp); + return false; + } + continue; + } + } + if (!unicode::IsIdentifierPart(char16_t(c))) { if (c != '\\' || !matchUnicodeEscapeIdent(&qc)) break; + + if (MOZ_UNLIKELY(unicode::IsSupplementary(qc))) { + char16_t lead, trail; + unicode::UTF16Encode(qc, &lead, &trail); + if (!tokenbuf.append(lead) || !tokenbuf.append(trail)) { + userbuf.setAddressOfNextRawChar(tmp); + return false; + } + continue; + } + c = qc; } + if (!tokenbuf.append(c)) { userbuf.setAddressOfNextRawChar(tmp); return false; @@ -1168,12 +1255,23 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) static_assert('_' < 128, "IdentifierStart contains '_', but as !IsUnicodeIDStart('_'), " "ensure that '_' is never handled here"); - if (unicode::IsUnicodeIDStart(c)) { + if (unicode::IsUnicodeIDStart(char16_t(c))) { identStart = userbuf.addressOfNextRawChar() - 1; hadUnicodeEscape = false; goto identifier; } + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) && + unicode::IsUnicodeIDStart(codePoint)) + { + identStart = userbuf.addressOfNextRawChar() - 2; + hadUnicodeEscape = false; + goto identifier; + } + } + goto badchar; } @@ -1224,6 +1322,17 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) c = getCharIgnoreEOL(); if (c == EOF) break; + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, nullptr, &codePoint)) { + if (!unicode::IsIdentifierPart(codePoint)) + break; + + continue; + } + } + if (!unicode::IsIdentifierPart(char16_t(c))) { if (c != '\\' || !matchUnicodeEscapeIdent(&qc)) break; @@ -1318,9 +1427,21 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } ungetCharIgnoreEOL(c); - if (c != EOF && unicode::IsIdentifierStart(char16_t(c))) { - reportError(JSMSG_IDSTART_AFTER_NUMBER); - goto error; + if (c != EOF) { + if (unicode::IsIdentifierStart(char16_t(c))) { + reportError(JSMSG_IDSTART_AFTER_NUMBER); + goto error; + } + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) && + unicode::IsIdentifierStart(codePoint)) + { + reportError(JSMSG_IDSTART_AFTER_NUMBER); + goto error; + } + } } // Unlike identifiers and strings, numbers cannot contain escaped @@ -1425,9 +1546,21 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } ungetCharIgnoreEOL(c); - if (c != EOF && unicode::IsIdentifierStart(char16_t(c))) { - reportError(JSMSG_IDSTART_AFTER_NUMBER); - goto error; + if (c != EOF) { + if (unicode::IsIdentifierStart(char16_t(c))) { + reportError(JSMSG_IDSTART_AFTER_NUMBER); + goto error; + } + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) && + unicode::IsIdentifierStart(codePoint)) + { + reportError(JSMSG_IDSTART_AFTER_NUMBER); + goto error; + } + } } double dval; diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 29dcead62..5d6b4b795 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -952,6 +952,7 @@ class MOZ_STACK_CLASS TokenStream uint32_t peekExtendedUnicodeEscape(uint32_t* codePoint); uint32_t matchUnicodeEscapeIdStart(uint32_t* codePoint); bool matchUnicodeEscapeIdent(uint32_t* codePoint); + bool matchTrailForLeadSurrogate(char16_t lead, char16_t* trail, uint32_t* codePoint); bool peekChars(int n, char16_t* cp); MOZ_MUST_USE bool getDirectives(bool isMultiline, bool shouldWarnDeprecated); diff --git a/js/src/tests/ecma_6/Syntax/identifiers-with-extended-unicode-escape.js b/js/src/tests/ecma_6/Syntax/identifiers-with-extended-unicode-escape.js index 8e0a05fb5..e4b5f4602 100644 --- a/js/src/tests/ecma_6/Syntax/identifiers-with-extended-unicode-escape.js +++ b/js/src/tests/ecma_6/Syntax/identifiers-with-extended-unicode-escape.js @@ -98,7 +98,7 @@ const otherIdContinue = [ 0x19DA, // NEW TAI LUE THAM DIGIT ONE, Gc=No ]; -for (let ident of [...idStart, ...otherIdStart]) { +for (let ident of [...idStart, ...otherIdStart, ...idStartSupplemental]) { for (let count of leadingZeros) { let zeros = "0".repeat(count); eval(` @@ -108,20 +108,13 @@ for (let ident of [...idStart, ...otherIdStart]) { } } -// Move this to the loop above when Bug 1197230 is fixed. -for (let ident of [...idStartSupplemental]) { - for (let zeros of leadingZeros) { - assertThrowsInstanceOf(() => eval(`\\u{${zeros}${ident.toString(16)}}`), SyntaxError); - } -} - for (let ident of [...idContinue, ...idContinueSupplemental, ...otherIdContinue]) { for (let zeros of leadingZeros) { assertThrowsInstanceOf(() => eval(`\\u{${zeros}${ident.toString(16)}}`), SyntaxError); } } -for (let ident of [...idStart, ...otherIdStart, ...idContinue, ...otherIdContinue]) { +for (let ident of [...idStart, ...otherIdStart, ...idContinue, ...otherIdContinue, ...idStartSupplemental, ...idContinueSupplemental]) { for (let zeros of leadingZeros) { eval(` let A\\u{${zeros}${ident.toString(16)}} = 123; @@ -130,13 +123,6 @@ for (let ident of [...idStart, ...otherIdStart, ...idContinue, ...otherIdContinu } } -// Move this to the loop above when Bug 1197230 is fixed. -for (let ident of [...idStartSupplemental, ...idContinueSupplemental]) { - for (let zeros of leadingZeros) { - assertThrowsInstanceOf(() => eval(`\\u{${zeros}${ident.toString(16)}}`), SyntaxError); - } -} - const notIdentifiers = [ 0x0000, // NULL, Gc=Cc diff --git a/js/src/vm/Unicode.cpp b/js/src/vm/Unicode.cpp index f4acf8f31..82541c231 100644 --- a/js/src/vm/Unicode.cpp +++ b/js/src/vm/Unicode.cpp @@ -1748,3 +1748,882 @@ const uint8_t unicode::folding_index2[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +bool +js::unicode::IsIdentifierStartNonBMP(uint32_t codePoint) +{ + if (codePoint >= 0x10000 && codePoint <= 0x1000b) + return true; + if (codePoint >= 0x1000d && codePoint <= 0x10026) + return true; + if (codePoint >= 0x10028 && codePoint <= 0x1003a) + return true; + if (codePoint >= 0x1003c && codePoint <= 0x1003d) + return true; + if (codePoint >= 0x1003f && codePoint <= 0x1004d) + return true; + if (codePoint >= 0x10050 && codePoint <= 0x1005d) + return true; + if (codePoint >= 0x10080 && codePoint <= 0x100fa) + return true; + if (codePoint >= 0x10140 && codePoint <= 0x10174) + return true; + if (codePoint >= 0x10280 && codePoint <= 0x1029c) + return true; + if (codePoint >= 0x102a0 && codePoint <= 0x102d0) + return true; + if (codePoint >= 0x10300 && codePoint <= 0x1031f) + return true; + if (codePoint >= 0x10330 && codePoint <= 0x1034a) + return true; + if (codePoint >= 0x10350 && codePoint <= 0x10375) + return true; + if (codePoint >= 0x10380 && codePoint <= 0x1039d) + return true; + if (codePoint >= 0x103a0 && codePoint <= 0x103c3) + return true; + if (codePoint >= 0x103c8 && codePoint <= 0x103cf) + return true; + if (codePoint >= 0x103d1 && codePoint <= 0x103d5) + return true; + if (codePoint >= 0x10400 && codePoint <= 0x1049d) + return true; + if (codePoint >= 0x104b0 && codePoint <= 0x104d3) + return true; + if (codePoint >= 0x104d8 && codePoint <= 0x104fb) + return true; + if (codePoint >= 0x10500 && codePoint <= 0x10527) + return true; + if (codePoint >= 0x10530 && codePoint <= 0x10563) + return true; + if (codePoint >= 0x10600 && codePoint <= 0x10736) + return true; + if (codePoint >= 0x10740 && codePoint <= 0x10755) + return true; + if (codePoint >= 0x10760 && codePoint <= 0x10767) + return true; + if (codePoint >= 0x10800 && codePoint <= 0x10805) + return true; + if (codePoint >= 0x10808 && codePoint <= 0x10808) + return true; + if (codePoint >= 0x1080a && codePoint <= 0x10835) + return true; + if (codePoint >= 0x10837 && codePoint <= 0x10838) + return true; + if (codePoint >= 0x1083c && codePoint <= 0x1083c) + return true; + if (codePoint >= 0x1083f && codePoint <= 0x10855) + return true; + if (codePoint >= 0x10860 && codePoint <= 0x10876) + return true; + if (codePoint >= 0x10880 && codePoint <= 0x1089e) + return true; + if (codePoint >= 0x108e0 && codePoint <= 0x108f2) + return true; + if (codePoint >= 0x108f4 && codePoint <= 0x108f5) + return true; + if (codePoint >= 0x10900 && codePoint <= 0x10915) + return true; + if (codePoint >= 0x10920 && codePoint <= 0x10939) + return true; + if (codePoint >= 0x10980 && codePoint <= 0x109b7) + return true; + if (codePoint >= 0x109be && codePoint <= 0x109bf) + return true; + if (codePoint >= 0x10a00 && codePoint <= 0x10a00) + return true; + if (codePoint >= 0x10a10 && codePoint <= 0x10a13) + return true; + if (codePoint >= 0x10a15 && codePoint <= 0x10a17) + return true; + if (codePoint >= 0x10a19 && codePoint <= 0x10a33) + return true; + if (codePoint >= 0x10a60 && codePoint <= 0x10a7c) + return true; + if (codePoint >= 0x10a80 && codePoint <= 0x10a9c) + return true; + if (codePoint >= 0x10ac0 && codePoint <= 0x10ac7) + return true; + if (codePoint >= 0x10ac9 && codePoint <= 0x10ae4) + return true; + if (codePoint >= 0x10b00 && codePoint <= 0x10b35) + return true; + if (codePoint >= 0x10b40 && codePoint <= 0x10b55) + return true; + if (codePoint >= 0x10b60 && codePoint <= 0x10b72) + return true; + if (codePoint >= 0x10b80 && codePoint <= 0x10b91) + return true; + if (codePoint >= 0x10c00 && codePoint <= 0x10c48) + return true; + if (codePoint >= 0x10c80 && codePoint <= 0x10cb2) + return true; + if (codePoint >= 0x10cc0 && codePoint <= 0x10cf2) + return true; + if (codePoint >= 0x11003 && codePoint <= 0x11037) + return true; + if (codePoint >= 0x11083 && codePoint <= 0x110af) + return true; + if (codePoint >= 0x110d0 && codePoint <= 0x110e8) + return true; + if (codePoint >= 0x11103 && codePoint <= 0x11126) + return true; + if (codePoint >= 0x11150 && codePoint <= 0x11172) + return true; + if (codePoint >= 0x11176 && codePoint <= 0x11176) + return true; + if (codePoint >= 0x11183 && codePoint <= 0x111b2) + return true; + if (codePoint >= 0x111c1 && codePoint <= 0x111c4) + return true; + if (codePoint >= 0x111da && codePoint <= 0x111da) + return true; + if (codePoint >= 0x111dc && codePoint <= 0x111dc) + return true; + if (codePoint >= 0x11200 && codePoint <= 0x11211) + return true; + if (codePoint >= 0x11213 && codePoint <= 0x1122b) + return true; + if (codePoint >= 0x11280 && codePoint <= 0x11286) + return true; + if (codePoint >= 0x11288 && codePoint <= 0x11288) + return true; + if (codePoint >= 0x1128a && codePoint <= 0x1128d) + return true; + if (codePoint >= 0x1128f && codePoint <= 0x1129d) + return true; + if (codePoint >= 0x1129f && codePoint <= 0x112a8) + return true; + if (codePoint >= 0x112b0 && codePoint <= 0x112de) + return true; + if (codePoint >= 0x11305 && codePoint <= 0x1130c) + return true; + if (codePoint >= 0x1130f && codePoint <= 0x11310) + return true; + if (codePoint >= 0x11313 && codePoint <= 0x11328) + return true; + if (codePoint >= 0x1132a && codePoint <= 0x11330) + return true; + if (codePoint >= 0x11332 && codePoint <= 0x11333) + return true; + if (codePoint >= 0x11335 && codePoint <= 0x11339) + return true; + if (codePoint >= 0x1133d && codePoint <= 0x1133d) + return true; + if (codePoint >= 0x11350 && codePoint <= 0x11350) + return true; + if (codePoint >= 0x1135d && codePoint <= 0x11361) + return true; + if (codePoint >= 0x11400 && codePoint <= 0x11434) + return true; + if (codePoint >= 0x11447 && codePoint <= 0x1144a) + return true; + if (codePoint >= 0x11480 && codePoint <= 0x114af) + return true; + if (codePoint >= 0x114c4 && codePoint <= 0x114c5) + return true; + if (codePoint >= 0x114c7 && codePoint <= 0x114c7) + return true; + if (codePoint >= 0x11580 && codePoint <= 0x115ae) + return true; + if (codePoint >= 0x115d8 && codePoint <= 0x115db) + return true; + if (codePoint >= 0x11600 && codePoint <= 0x1162f) + return true; + if (codePoint >= 0x11644 && codePoint <= 0x11644) + return true; + if (codePoint >= 0x11680 && codePoint <= 0x116aa) + return true; + if (codePoint >= 0x11700 && codePoint <= 0x11719) + return true; + if (codePoint >= 0x118a0 && codePoint <= 0x118df) + return true; + if (codePoint >= 0x118ff && codePoint <= 0x118ff) + return true; + if (codePoint >= 0x11ac0 && codePoint <= 0x11af8) + return true; + if (codePoint >= 0x11c00 && codePoint <= 0x11c08) + return true; + if (codePoint >= 0x11c0a && codePoint <= 0x11c2e) + return true; + if (codePoint >= 0x11c40 && codePoint <= 0x11c40) + return true; + if (codePoint >= 0x11c72 && codePoint <= 0x11c8f) + return true; + if (codePoint >= 0x12000 && codePoint <= 0x12399) + return true; + if (codePoint >= 0x12400 && codePoint <= 0x1246e) + return true; + if (codePoint >= 0x12480 && codePoint <= 0x12543) + return true; + if (codePoint >= 0x13000 && codePoint <= 0x1342e) + return true; + if (codePoint >= 0x14400 && codePoint <= 0x14646) + return true; + if (codePoint >= 0x16800 && codePoint <= 0x16a38) + return true; + if (codePoint >= 0x16a40 && codePoint <= 0x16a5e) + return true; + if (codePoint >= 0x16ad0 && codePoint <= 0x16aed) + return true; + if (codePoint >= 0x16b00 && codePoint <= 0x16b2f) + return true; + if (codePoint >= 0x16b40 && codePoint <= 0x16b43) + return true; + if (codePoint >= 0x16b63 && codePoint <= 0x16b77) + return true; + if (codePoint >= 0x16b7d && codePoint <= 0x16b8f) + return true; + if (codePoint >= 0x16f00 && codePoint <= 0x16f44) + return true; + if (codePoint >= 0x16f50 && codePoint <= 0x16f50) + return true; + if (codePoint >= 0x16f93 && codePoint <= 0x16f9f) + return true; + if (codePoint >= 0x16fe0 && codePoint <= 0x16fe0) + return true; + if (codePoint >= 0x17000 && codePoint <= 0x187ec) + return true; + if (codePoint >= 0x18800 && codePoint <= 0x18af2) + return true; + if (codePoint >= 0x1b000 && codePoint <= 0x1b001) + return true; + if (codePoint >= 0x1bc00 && codePoint <= 0x1bc6a) + return true; + if (codePoint >= 0x1bc70 && codePoint <= 0x1bc7c) + return true; + if (codePoint >= 0x1bc80 && codePoint <= 0x1bc88) + return true; + if (codePoint >= 0x1bc90 && codePoint <= 0x1bc99) + return true; + if (codePoint >= 0x1d400 && codePoint <= 0x1d454) + return true; + if (codePoint >= 0x1d456 && codePoint <= 0x1d49c) + return true; + if (codePoint >= 0x1d49e && codePoint <= 0x1d49f) + return true; + if (codePoint >= 0x1d4a2 && codePoint <= 0x1d4a2) + return true; + if (codePoint >= 0x1d4a5 && codePoint <= 0x1d4a6) + return true; + if (codePoint >= 0x1d4a9 && codePoint <= 0x1d4ac) + return true; + if (codePoint >= 0x1d4ae && codePoint <= 0x1d4b9) + return true; + if (codePoint >= 0x1d4bb && codePoint <= 0x1d4bb) + return true; + if (codePoint >= 0x1d4bd && codePoint <= 0x1d4c3) + return true; + if (codePoint >= 0x1d4c5 && codePoint <= 0x1d505) + return true; + if (codePoint >= 0x1d507 && codePoint <= 0x1d50a) + return true; + if (codePoint >= 0x1d50d && codePoint <= 0x1d514) + return true; + if (codePoint >= 0x1d516 && codePoint <= 0x1d51c) + return true; + if (codePoint >= 0x1d51e && codePoint <= 0x1d539) + return true; + if (codePoint >= 0x1d53b && codePoint <= 0x1d53e) + return true; + if (codePoint >= 0x1d540 && codePoint <= 0x1d544) + return true; + if (codePoint >= 0x1d546 && codePoint <= 0x1d546) + return true; + if (codePoint >= 0x1d54a && codePoint <= 0x1d550) + return true; + if (codePoint >= 0x1d552 && codePoint <= 0x1d6a5) + return true; + if (codePoint >= 0x1d6a8 && codePoint <= 0x1d6c0) + return true; + if (codePoint >= 0x1d6c2 && codePoint <= 0x1d6da) + return true; + if (codePoint >= 0x1d6dc && codePoint <= 0x1d6fa) + return true; + if (codePoint >= 0x1d6fc && codePoint <= 0x1d714) + return true; + if (codePoint >= 0x1d716 && codePoint <= 0x1d734) + return true; + if (codePoint >= 0x1d736 && codePoint <= 0x1d74e) + return true; + if (codePoint >= 0x1d750 && codePoint <= 0x1d76e) + return true; + if (codePoint >= 0x1d770 && codePoint <= 0x1d788) + return true; + if (codePoint >= 0x1d78a && codePoint <= 0x1d7a8) + return true; + if (codePoint >= 0x1d7aa && codePoint <= 0x1d7c2) + return true; + if (codePoint >= 0x1d7c4 && codePoint <= 0x1d7cb) + return true; + if (codePoint >= 0x1e800 && codePoint <= 0x1e8c4) + return true; + if (codePoint >= 0x1e900 && codePoint <= 0x1e943) + return true; + if (codePoint >= 0x1ee00 && codePoint <= 0x1ee03) + return true; + if (codePoint >= 0x1ee05 && codePoint <= 0x1ee1f) + return true; + if (codePoint >= 0x1ee21 && codePoint <= 0x1ee22) + return true; + if (codePoint >= 0x1ee24 && codePoint <= 0x1ee24) + return true; + if (codePoint >= 0x1ee27 && codePoint <= 0x1ee27) + return true; + if (codePoint >= 0x1ee29 && codePoint <= 0x1ee32) + return true; + if (codePoint >= 0x1ee34 && codePoint <= 0x1ee37) + return true; + if (codePoint >= 0x1ee39 && codePoint <= 0x1ee39) + return true; + if (codePoint >= 0x1ee3b && codePoint <= 0x1ee3b) + return true; + if (codePoint >= 0x1ee42 && codePoint <= 0x1ee42) + return true; + if (codePoint >= 0x1ee47 && codePoint <= 0x1ee47) + return true; + if (codePoint >= 0x1ee49 && codePoint <= 0x1ee49) + return true; + if (codePoint >= 0x1ee4b && codePoint <= 0x1ee4b) + return true; + if (codePoint >= 0x1ee4d && codePoint <= 0x1ee4f) + return true; + if (codePoint >= 0x1ee51 && codePoint <= 0x1ee52) + return true; + if (codePoint >= 0x1ee54 && codePoint <= 0x1ee54) + return true; + if (codePoint >= 0x1ee57 && codePoint <= 0x1ee57) + return true; + if (codePoint >= 0x1ee59 && codePoint <= 0x1ee59) + return true; + if (codePoint >= 0x1ee5b && codePoint <= 0x1ee5b) + return true; + if (codePoint >= 0x1ee5d && codePoint <= 0x1ee5d) + return true; + if (codePoint >= 0x1ee5f && codePoint <= 0x1ee5f) + return true; + if (codePoint >= 0x1ee61 && codePoint <= 0x1ee62) + return true; + if (codePoint >= 0x1ee64 && codePoint <= 0x1ee64) + return true; + if (codePoint >= 0x1ee67 && codePoint <= 0x1ee6a) + return true; + if (codePoint >= 0x1ee6c && codePoint <= 0x1ee72) + return true; + if (codePoint >= 0x1ee74 && codePoint <= 0x1ee77) + return true; + if (codePoint >= 0x1ee79 && codePoint <= 0x1ee7c) + return true; + if (codePoint >= 0x1ee7e && codePoint <= 0x1ee7e) + return true; + if (codePoint >= 0x1ee80 && codePoint <= 0x1ee89) + return true; + if (codePoint >= 0x1ee8b && codePoint <= 0x1ee9b) + return true; + if (codePoint >= 0x1eea1 && codePoint <= 0x1eea3) + return true; + if (codePoint >= 0x1eea5 && codePoint <= 0x1eea9) + return true; + if (codePoint >= 0x1eeab && codePoint <= 0x1eebb) + return true; + if (codePoint >= 0x20000 && codePoint <= 0x2a6d6) + return true; + if (codePoint >= 0x2a700 && codePoint <= 0x2b734) + return true; + if (codePoint >= 0x2b740 && codePoint <= 0x2b81d) + return true; + if (codePoint >= 0x2b820 && codePoint <= 0x2cea1) + return true; + if (codePoint >= 0x2f800 && codePoint <= 0x2fa1d) + return true; + return false; +} + +bool +js::unicode::IsIdentifierPartNonBMP(uint32_t codePoint) +{ + if (codePoint >= 0x10000 && codePoint <= 0x1000b) + return true; + if (codePoint >= 0x1000d && codePoint <= 0x10026) + return true; + if (codePoint >= 0x10028 && codePoint <= 0x1003a) + return true; + if (codePoint >= 0x1003c && codePoint <= 0x1003d) + return true; + if (codePoint >= 0x1003f && codePoint <= 0x1004d) + return true; + if (codePoint >= 0x10050 && codePoint <= 0x1005d) + return true; + if (codePoint >= 0x10080 && codePoint <= 0x100fa) + return true; + if (codePoint >= 0x10140 && codePoint <= 0x10174) + return true; + if (codePoint >= 0x101fd && codePoint <= 0x101fd) + return true; + if (codePoint >= 0x10280 && codePoint <= 0x1029c) + return true; + if (codePoint >= 0x102a0 && codePoint <= 0x102d0) + return true; + if (codePoint >= 0x102e0 && codePoint <= 0x102e0) + return true; + if (codePoint >= 0x10300 && codePoint <= 0x1031f) + return true; + if (codePoint >= 0x10330 && codePoint <= 0x1034a) + return true; + if (codePoint >= 0x10350 && codePoint <= 0x1037a) + return true; + if (codePoint >= 0x10380 && codePoint <= 0x1039d) + return true; + if (codePoint >= 0x103a0 && codePoint <= 0x103c3) + return true; + if (codePoint >= 0x103c8 && codePoint <= 0x103cf) + return true; + if (codePoint >= 0x103d1 && codePoint <= 0x103d5) + return true; + if (codePoint >= 0x10400 && codePoint <= 0x1049d) + return true; + if (codePoint >= 0x104a0 && codePoint <= 0x104a9) + return true; + if (codePoint >= 0x104b0 && codePoint <= 0x104d3) + return true; + if (codePoint >= 0x104d8 && codePoint <= 0x104fb) + return true; + if (codePoint >= 0x10500 && codePoint <= 0x10527) + return true; + if (codePoint >= 0x10530 && codePoint <= 0x10563) + return true; + if (codePoint >= 0x10600 && codePoint <= 0x10736) + return true; + if (codePoint >= 0x10740 && codePoint <= 0x10755) + return true; + if (codePoint >= 0x10760 && codePoint <= 0x10767) + return true; + if (codePoint >= 0x10800 && codePoint <= 0x10805) + return true; + if (codePoint >= 0x10808 && codePoint <= 0x10808) + return true; + if (codePoint >= 0x1080a && codePoint <= 0x10835) + return true; + if (codePoint >= 0x10837 && codePoint <= 0x10838) + return true; + if (codePoint >= 0x1083c && codePoint <= 0x1083c) + return true; + if (codePoint >= 0x1083f && codePoint <= 0x10855) + return true; + if (codePoint >= 0x10860 && codePoint <= 0x10876) + return true; + if (codePoint >= 0x10880 && codePoint <= 0x1089e) + return true; + if (codePoint >= 0x108e0 && codePoint <= 0x108f2) + return true; + if (codePoint >= 0x108f4 && codePoint <= 0x108f5) + return true; + if (codePoint >= 0x10900 && codePoint <= 0x10915) + return true; + if (codePoint >= 0x10920 && codePoint <= 0x10939) + return true; + if (codePoint >= 0x10980 && codePoint <= 0x109b7) + return true; + if (codePoint >= 0x109be && codePoint <= 0x109bf) + return true; + if (codePoint >= 0x10a00 && codePoint <= 0x10a03) + return true; + if (codePoint >= 0x10a05 && codePoint <= 0x10a06) + return true; + if (codePoint >= 0x10a0c && codePoint <= 0x10a13) + return true; + if (codePoint >= 0x10a15 && codePoint <= 0x10a17) + return true; + if (codePoint >= 0x10a19 && codePoint <= 0x10a33) + return true; + if (codePoint >= 0x10a38 && codePoint <= 0x10a3a) + return true; + if (codePoint >= 0x10a3f && codePoint <= 0x10a3f) + return true; + if (codePoint >= 0x10a60 && codePoint <= 0x10a7c) + return true; + if (codePoint >= 0x10a80 && codePoint <= 0x10a9c) + return true; + if (codePoint >= 0x10ac0 && codePoint <= 0x10ac7) + return true; + if (codePoint >= 0x10ac9 && codePoint <= 0x10ae6) + return true; + if (codePoint >= 0x10b00 && codePoint <= 0x10b35) + return true; + if (codePoint >= 0x10b40 && codePoint <= 0x10b55) + return true; + if (codePoint >= 0x10b60 && codePoint <= 0x10b72) + return true; + if (codePoint >= 0x10b80 && codePoint <= 0x10b91) + return true; + if (codePoint >= 0x10c00 && codePoint <= 0x10c48) + return true; + if (codePoint >= 0x10c80 && codePoint <= 0x10cb2) + return true; + if (codePoint >= 0x10cc0 && codePoint <= 0x10cf2) + return true; + if (codePoint >= 0x11000 && codePoint <= 0x11046) + return true; + if (codePoint >= 0x11066 && codePoint <= 0x1106f) + return true; + if (codePoint >= 0x1107f && codePoint <= 0x110ba) + return true; + if (codePoint >= 0x110d0 && codePoint <= 0x110e8) + return true; + if (codePoint >= 0x110f0 && codePoint <= 0x110f9) + return true; + if (codePoint >= 0x11100 && codePoint <= 0x11134) + return true; + if (codePoint >= 0x11136 && codePoint <= 0x1113f) + return true; + if (codePoint >= 0x11150 && codePoint <= 0x11173) + return true; + if (codePoint >= 0x11176 && codePoint <= 0x11176) + return true; + if (codePoint >= 0x11180 && codePoint <= 0x111c4) + return true; + if (codePoint >= 0x111ca && codePoint <= 0x111cc) + return true; + if (codePoint >= 0x111d0 && codePoint <= 0x111da) + return true; + if (codePoint >= 0x111dc && codePoint <= 0x111dc) + return true; + if (codePoint >= 0x11200 && codePoint <= 0x11211) + return true; + if (codePoint >= 0x11213 && codePoint <= 0x11237) + return true; + if (codePoint >= 0x1123e && codePoint <= 0x1123e) + return true; + if (codePoint >= 0x11280 && codePoint <= 0x11286) + return true; + if (codePoint >= 0x11288 && codePoint <= 0x11288) + return true; + if (codePoint >= 0x1128a && codePoint <= 0x1128d) + return true; + if (codePoint >= 0x1128f && codePoint <= 0x1129d) + return true; + if (codePoint >= 0x1129f && codePoint <= 0x112a8) + return true; + if (codePoint >= 0x112b0 && codePoint <= 0x112ea) + return true; + if (codePoint >= 0x112f0 && codePoint <= 0x112f9) + return true; + if (codePoint >= 0x11300 && codePoint <= 0x11303) + return true; + if (codePoint >= 0x11305 && codePoint <= 0x1130c) + return true; + if (codePoint >= 0x1130f && codePoint <= 0x11310) + return true; + if (codePoint >= 0x11313 && codePoint <= 0x11328) + return true; + if (codePoint >= 0x1132a && codePoint <= 0x11330) + return true; + if (codePoint >= 0x11332 && codePoint <= 0x11333) + return true; + if (codePoint >= 0x11335 && codePoint <= 0x11339) + return true; + if (codePoint >= 0x1133c && codePoint <= 0x11344) + return true; + if (codePoint >= 0x11347 && codePoint <= 0x11348) + return true; + if (codePoint >= 0x1134b && codePoint <= 0x1134d) + return true; + if (codePoint >= 0x11350 && codePoint <= 0x11350) + return true; + if (codePoint >= 0x11357 && codePoint <= 0x11357) + return true; + if (codePoint >= 0x1135d && codePoint <= 0x11363) + return true; + if (codePoint >= 0x11366 && codePoint <= 0x1136c) + return true; + if (codePoint >= 0x11370 && codePoint <= 0x11374) + return true; + if (codePoint >= 0x11400 && codePoint <= 0x1144a) + return true; + if (codePoint >= 0x11450 && codePoint <= 0x11459) + return true; + if (codePoint >= 0x11480 && codePoint <= 0x114c5) + return true; + if (codePoint >= 0x114c7 && codePoint <= 0x114c7) + return true; + if (codePoint >= 0x114d0 && codePoint <= 0x114d9) + return true; + if (codePoint >= 0x11580 && codePoint <= 0x115b5) + return true; + if (codePoint >= 0x115b8 && codePoint <= 0x115c0) + return true; + if (codePoint >= 0x115d8 && codePoint <= 0x115dd) + return true; + if (codePoint >= 0x11600 && codePoint <= 0x11640) + return true; + if (codePoint >= 0x11644 && codePoint <= 0x11644) + return true; + if (codePoint >= 0x11650 && codePoint <= 0x11659) + return true; + if (codePoint >= 0x11680 && codePoint <= 0x116b7) + return true; + if (codePoint >= 0x116c0 && codePoint <= 0x116c9) + return true; + if (codePoint >= 0x11700 && codePoint <= 0x11719) + return true; + if (codePoint >= 0x1171d && codePoint <= 0x1172b) + return true; + if (codePoint >= 0x11730 && codePoint <= 0x11739) + return true; + if (codePoint >= 0x118a0 && codePoint <= 0x118e9) + return true; + if (codePoint >= 0x118ff && codePoint <= 0x118ff) + return true; + if (codePoint >= 0x11ac0 && codePoint <= 0x11af8) + return true; + if (codePoint >= 0x11c00 && codePoint <= 0x11c08) + return true; + if (codePoint >= 0x11c0a && codePoint <= 0x11c36) + return true; + if (codePoint >= 0x11c38 && codePoint <= 0x11c40) + return true; + if (codePoint >= 0x11c50 && codePoint <= 0x11c59) + return true; + if (codePoint >= 0x11c72 && codePoint <= 0x11c8f) + return true; + if (codePoint >= 0x11c92 && codePoint <= 0x11ca7) + return true; + if (codePoint >= 0x11ca9 && codePoint <= 0x11cb6) + return true; + if (codePoint >= 0x12000 && codePoint <= 0x12399) + return true; + if (codePoint >= 0x12400 && codePoint <= 0x1246e) + return true; + if (codePoint >= 0x12480 && codePoint <= 0x12543) + return true; + if (codePoint >= 0x13000 && codePoint <= 0x1342e) + return true; + if (codePoint >= 0x14400 && codePoint <= 0x14646) + return true; + if (codePoint >= 0x16800 && codePoint <= 0x16a38) + return true; + if (codePoint >= 0x16a40 && codePoint <= 0x16a5e) + return true; + if (codePoint >= 0x16a60 && codePoint <= 0x16a69) + return true; + if (codePoint >= 0x16ad0 && codePoint <= 0x16aed) + return true; + if (codePoint >= 0x16af0 && codePoint <= 0x16af4) + return true; + if (codePoint >= 0x16b00 && codePoint <= 0x16b36) + return true; + if (codePoint >= 0x16b40 && codePoint <= 0x16b43) + return true; + if (codePoint >= 0x16b50 && codePoint <= 0x16b59) + return true; + if (codePoint >= 0x16b63 && codePoint <= 0x16b77) + return true; + if (codePoint >= 0x16b7d && codePoint <= 0x16b8f) + return true; + if (codePoint >= 0x16f00 && codePoint <= 0x16f44) + return true; + if (codePoint >= 0x16f50 && codePoint <= 0x16f7e) + return true; + if (codePoint >= 0x16f8f && codePoint <= 0x16f9f) + return true; + if (codePoint >= 0x16fe0 && codePoint <= 0x16fe0) + return true; + if (codePoint >= 0x17000 && codePoint <= 0x187ec) + return true; + if (codePoint >= 0x18800 && codePoint <= 0x18af2) + return true; + if (codePoint >= 0x1b000 && codePoint <= 0x1b001) + return true; + if (codePoint >= 0x1bc00 && codePoint <= 0x1bc6a) + return true; + if (codePoint >= 0x1bc70 && codePoint <= 0x1bc7c) + return true; + if (codePoint >= 0x1bc80 && codePoint <= 0x1bc88) + return true; + if (codePoint >= 0x1bc90 && codePoint <= 0x1bc99) + return true; + if (codePoint >= 0x1bc9d && codePoint <= 0x1bc9e) + return true; + if (codePoint >= 0x1d165 && codePoint <= 0x1d169) + return true; + if (codePoint >= 0x1d16d && codePoint <= 0x1d172) + return true; + if (codePoint >= 0x1d17b && codePoint <= 0x1d182) + return true; + if (codePoint >= 0x1d185 && codePoint <= 0x1d18b) + return true; + if (codePoint >= 0x1d1aa && codePoint <= 0x1d1ad) + return true; + if (codePoint >= 0x1d242 && codePoint <= 0x1d244) + return true; + if (codePoint >= 0x1d400 && codePoint <= 0x1d454) + return true; + if (codePoint >= 0x1d456 && codePoint <= 0x1d49c) + return true; + if (codePoint >= 0x1d49e && codePoint <= 0x1d49f) + return true; + if (codePoint >= 0x1d4a2 && codePoint <= 0x1d4a2) + return true; + if (codePoint >= 0x1d4a5 && codePoint <= 0x1d4a6) + return true; + if (codePoint >= 0x1d4a9 && codePoint <= 0x1d4ac) + return true; + if (codePoint >= 0x1d4ae && codePoint <= 0x1d4b9) + return true; + if (codePoint >= 0x1d4bb && codePoint <= 0x1d4bb) + return true; + if (codePoint >= 0x1d4bd && codePoint <= 0x1d4c3) + return true; + if (codePoint >= 0x1d4c5 && codePoint <= 0x1d505) + return true; + if (codePoint >= 0x1d507 && codePoint <= 0x1d50a) + return true; + if (codePoint >= 0x1d50d && codePoint <= 0x1d514) + return true; + if (codePoint >= 0x1d516 && codePoint <= 0x1d51c) + return true; + if (codePoint >= 0x1d51e && codePoint <= 0x1d539) + return true; + if (codePoint >= 0x1d53b && codePoint <= 0x1d53e) + return true; + if (codePoint >= 0x1d540 && codePoint <= 0x1d544) + return true; + if (codePoint >= 0x1d546 && codePoint <= 0x1d546) + return true; + if (codePoint >= 0x1d54a && codePoint <= 0x1d550) + return true; + if (codePoint >= 0x1d552 && codePoint <= 0x1d6a5) + return true; + if (codePoint >= 0x1d6a8 && codePoint <= 0x1d6c0) + return true; + if (codePoint >= 0x1d6c2 && codePoint <= 0x1d6da) + return true; + if (codePoint >= 0x1d6dc && codePoint <= 0x1d6fa) + return true; + if (codePoint >= 0x1d6fc && codePoint <= 0x1d714) + return true; + if (codePoint >= 0x1d716 && codePoint <= 0x1d734) + return true; + if (codePoint >= 0x1d736 && codePoint <= 0x1d74e) + return true; + if (codePoint >= 0x1d750 && codePoint <= 0x1d76e) + return true; + if (codePoint >= 0x1d770 && codePoint <= 0x1d788) + return true; + if (codePoint >= 0x1d78a && codePoint <= 0x1d7a8) + return true; + if (codePoint >= 0x1d7aa && codePoint <= 0x1d7c2) + return true; + if (codePoint >= 0x1d7c4 && codePoint <= 0x1d7cb) + return true; + if (codePoint >= 0x1d7ce && codePoint <= 0x1d7ff) + return true; + if (codePoint >= 0x1da00 && codePoint <= 0x1da36) + return true; + if (codePoint >= 0x1da3b && codePoint <= 0x1da6c) + return true; + if (codePoint >= 0x1da75 && codePoint <= 0x1da75) + return true; + if (codePoint >= 0x1da84 && codePoint <= 0x1da84) + return true; + if (codePoint >= 0x1da9b && codePoint <= 0x1da9f) + return true; + if (codePoint >= 0x1daa1 && codePoint <= 0x1daaf) + return true; + if (codePoint >= 0x1e000 && codePoint <= 0x1e006) + return true; + if (codePoint >= 0x1e008 && codePoint <= 0x1e018) + return true; + if (codePoint >= 0x1e01b && codePoint <= 0x1e021) + return true; + if (codePoint >= 0x1e023 && codePoint <= 0x1e024) + return true; + if (codePoint >= 0x1e026 && codePoint <= 0x1e02a) + return true; + if (codePoint >= 0x1e800 && codePoint <= 0x1e8c4) + return true; + if (codePoint >= 0x1e8d0 && codePoint <= 0x1e8d6) + return true; + if (codePoint >= 0x1e900 && codePoint <= 0x1e94a) + return true; + if (codePoint >= 0x1e950 && codePoint <= 0x1e959) + return true; + if (codePoint >= 0x1ee00 && codePoint <= 0x1ee03) + return true; + if (codePoint >= 0x1ee05 && codePoint <= 0x1ee1f) + return true; + if (codePoint >= 0x1ee21 && codePoint <= 0x1ee22) + return true; + if (codePoint >= 0x1ee24 && codePoint <= 0x1ee24) + return true; + if (codePoint >= 0x1ee27 && codePoint <= 0x1ee27) + return true; + if (codePoint >= 0x1ee29 && codePoint <= 0x1ee32) + return true; + if (codePoint >= 0x1ee34 && codePoint <= 0x1ee37) + return true; + if (codePoint >= 0x1ee39 && codePoint <= 0x1ee39) + return true; + if (codePoint >= 0x1ee3b && codePoint <= 0x1ee3b) + return true; + if (codePoint >= 0x1ee42 && codePoint <= 0x1ee42) + return true; + if (codePoint >= 0x1ee47 && codePoint <= 0x1ee47) + return true; + if (codePoint >= 0x1ee49 && codePoint <= 0x1ee49) + return true; + if (codePoint >= 0x1ee4b && codePoint <= 0x1ee4b) + return true; + if (codePoint >= 0x1ee4d && codePoint <= 0x1ee4f) + return true; + if (codePoint >= 0x1ee51 && codePoint <= 0x1ee52) + return true; + if (codePoint >= 0x1ee54 && codePoint <= 0x1ee54) + return true; + if (codePoint >= 0x1ee57 && codePoint <= 0x1ee57) + return true; + if (codePoint >= 0x1ee59 && codePoint <= 0x1ee59) + return true; + if (codePoint >= 0x1ee5b && codePoint <= 0x1ee5b) + return true; + if (codePoint >= 0x1ee5d && codePoint <= 0x1ee5d) + return true; + if (codePoint >= 0x1ee5f && codePoint <= 0x1ee5f) + return true; + if (codePoint >= 0x1ee61 && codePoint <= 0x1ee62) + return true; + if (codePoint >= 0x1ee64 && codePoint <= 0x1ee64) + return true; + if (codePoint >= 0x1ee67 && codePoint <= 0x1ee6a) + return true; + if (codePoint >= 0x1ee6c && codePoint <= 0x1ee72) + return true; + if (codePoint >= 0x1ee74 && codePoint <= 0x1ee77) + return true; + if (codePoint >= 0x1ee79 && codePoint <= 0x1ee7c) + return true; + if (codePoint >= 0x1ee7e && codePoint <= 0x1ee7e) + return true; + if (codePoint >= 0x1ee80 && codePoint <= 0x1ee89) + return true; + if (codePoint >= 0x1ee8b && codePoint <= 0x1ee9b) + return true; + if (codePoint >= 0x1eea1 && codePoint <= 0x1eea3) + return true; + if (codePoint >= 0x1eea5 && codePoint <= 0x1eea9) + return true; + if (codePoint >= 0x1eeab && codePoint <= 0x1eebb) + return true; + if (codePoint >= 0x20000 && codePoint <= 0x2a6d6) + return true; + if (codePoint >= 0x2a700 && codePoint <= 0x2b734) + return true; + if (codePoint >= 0x2b740 && codePoint <= 0x2b81d) + return true; + if (codePoint >= 0x2b820 && codePoint <= 0x2cea1) + return true; + if (codePoint >= 0x2f800 && codePoint <= 0x2fa1d) + return true; + if (codePoint >= 0xe0100 && codePoint <= 0xe01ef) + return true; + return false; +} diff --git a/js/src/vm/Unicode.h b/js/src/vm/Unicode.h index 8b538d06d..bdac848fb 100644 --- a/js/src/vm/Unicode.h +++ b/js/src/vm/Unicode.h @@ -143,11 +143,15 @@ IsIdentifierStart(char16_t ch) return CharInfo(ch).isUnicodeIDStart(); } +bool +IsIdentifierStartNonBMP(uint32_t codePoint); + inline bool IsIdentifierStart(uint32_t codePoint) { - // TODO: Supplemental code points not yet supported (bug 1197230). - return codePoint <= UTF16Max && IsIdentifierStart(char16_t(codePoint)); + if (MOZ_UNLIKELY(codePoint > UTF16Max)) + return IsIdentifierStartNonBMP(codePoint); + return IsIdentifierStart(char16_t(codePoint)); } inline bool @@ -170,11 +174,16 @@ IsIdentifierPart(char16_t ch) return CharInfo(ch).isUnicodeIDContinue(); } + +bool +IsIdentifierPartNonBMP(uint32_t codePoint); + inline bool IsIdentifierPart(uint32_t codePoint) { - // TODO: Supplemental code points not yet supported (bug 1197230). - return codePoint <= UTF16Max && IsIdentifierPart(char16_t(codePoint)); + if (MOZ_UNLIKELY(codePoint > UTF16Max)) + return IsIdentifierPartNonBMP(codePoint); + return IsIdentifierPart(char16_t(codePoint)); } inline bool @@ -183,6 +192,17 @@ IsUnicodeIDStart(char16_t ch) return CharInfo(ch).isUnicodeIDStart(); } +bool +IsUnicodeIDStartNonBMP(uint32_t codePoint); + +inline bool +IsUnicodeIDStart(uint32_t codePoint) +{ + if (MOZ_UNLIKELY(codePoint > UTF16Max)) + return IsIdentifierStartNonBMP(codePoint); + return IsUnicodeIDStart(char16_t(codePoint)); +} + inline bool IsSpace(char16_t ch) { diff --git a/js/src/vm/UnicodeNonBMP.h b/js/src/vm/UnicodeNonBMP.h index 6cc64cde4..f99e227cd 100644 --- a/js/src/vm/UnicodeNonBMP.h +++ b/js/src/vm/UnicodeNonBMP.h @@ -10,6 +10,16 @@ #ifndef vm_UnicodeNonBMP_h #define vm_UnicodeNonBMP_h +// |macro| receives the following arguments +// macro(FROM, TO, LEAD, TRAIL_FROM, TRAIL_TO, DIFF) +// FROM: code point where the range starts +// TO: code point where the range ends +// LEAD: common lead surrogate of FROM and TO +// TRAIL_FROM: trail surrogate of FROM +// TRAIL_FROM: trail surrogate of TO +// DIFF: the difference between the code point in the range and +// converted code point + #define FOR_EACH_NON_BMP_LOWERCASE(macro) \ macro(0x10400, 0x10427, 0xd801, 0xdc00, 0xdc27, 40) \ macro(0x104b0, 0x104d3, 0xd801, 0xdcb0, 0xdcd3, 40) \ diff --git a/js/src/vm/make_unicode.py b/js/src/vm/make_unicode.py index 73c090ac9..83f0d004b 100755 --- a/js/src/vm/make_unicode.py +++ b/js/src/vm/make_unicode.py @@ -155,37 +155,65 @@ def utf16_encode(code): return lead, trail def make_non_bmp_convert_macro(out_file, name, convert_map): + # Find continuous range in convert_map. convert_list = [] entry = None for code in sorted(convert_map.keys()): + lead, trail = utf16_encode(code) converted = convert_map[code] diff = converted - code - if entry and code == entry['code'] + entry['length'] and diff == entry['diff']: + if (entry and code == entry['code'] + entry['length'] and + diff == entry['diff'] and lead == entry['lead']): + entry['length'] += 1 continue - entry = { 'code': code, 'diff': diff, 'length': 1 } + entry = { + 'code': code, + 'diff': diff, + 'length': 1, + 'lead': lead, + 'trail': trail, + } convert_list.append(entry) + # Generate macro call for each range. lines = [] for entry in convert_list: from_code = entry['code'] to_code = entry['code'] + entry['length'] - 1 diff = entry['diff'] - from_lead, from_trail = utf16_encode(from_code) - to_lead, to_trail = utf16_encode(to_code) - - assert from_lead == to_lead + lead = entry['lead'] + from_trail = entry['trail'] + to_trail = entry['trail'] + entry['length'] - 1 lines.append(' macro(0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, {:d})'.format( - from_code, to_code, from_lead, from_trail, to_trail, diff)) + from_code, to_code, lead, from_trail, to_trail, diff)) out_file.write('#define FOR_EACH_NON_BMP_{}(macro) \\\n'.format(name)) out_file.write(' \\\n'.join(lines)) out_file.write('\n') +def for_each_non_bmp_group(group_set): + # Find continuous range in group_set. + group_list = [] + entry = None + for code in sorted(group_set.keys()): + if entry and code == entry['code'] + entry['length']: + entry['length'] += 1 + continue + + entry = { + 'code': code, + 'length': 1 + } + group_list.append(entry) + + for entry in group_list: + yield (entry['code'], entry['code'] + entry['length'] - 1) + def process_derived_core_properties(derived_core_properties): id_start = set() id_continue = set() @@ -214,6 +242,9 @@ def process_unicode_data(unicode_data, derived_core_properties): non_bmp_lower_map = {} non_bmp_upper_map = {} + non_bmp_id_start_set = {} + non_bmp_id_cont_set = {} + non_bmp_space_set = {} (id_start, id_continue) = process_derived_core_properties(derived_core_properties) @@ -246,6 +277,13 @@ def process_unicode_data(unicode_data, derived_core_properties): non_bmp_lower_map[code] = lower if code != upper: non_bmp_upper_map[code] = upper + if category == 'Zs': + non_bmp_space_set[code] = 1 + test_space_table.append(code) + if code in id_start: + non_bmp_id_start_set[code] = 1 + if code in id_continue: + non_bmp_id_cont_set[code] = 1 continue # we combine whitespace and lineterminators because in pratice we don't need them separated @@ -315,6 +353,8 @@ def process_unicode_data(unicode_data, derived_core_properties): table, index, same_upper_table, same_upper_index, non_bmp_lower_map, non_bmp_upper_map, + non_bmp_space_set, + non_bmp_id_start_set, non_bmp_id_cont_set, test_table, test_space_table, ) @@ -412,6 +452,16 @@ def make_non_bmp_file(version, #ifndef vm_UnicodeNonBMP_h #define vm_UnicodeNonBMP_h +// |macro| receives the following arguments +// macro(FROM, TO, LEAD, TRAIL_FROM, TRAIL_TO, DIFF) +// FROM: code point where the range starts +// TO: code point where the range ends +// LEAD: common lead surrogate of FROM and TO +// TRAIL_FROM: trail surrogate of FROM +// TRAIL_FROM: trail surrogate of TO +// DIFF: the difference between the code point in the range and +// converted code point + """) make_non_bmp_convert_macro(non_bmp_file, 'LOWERCASE', non_bmp_lower_map) @@ -525,7 +575,9 @@ if (typeof reportCompare === "function") def make_unicode_file(version, table, index, same_upper_table, same_upper_index, - folding_table, folding_index): + folding_table, folding_index, + non_bmp_space_set, + non_bmp_id_start_set, non_bmp_id_cont_set): index1, index2, shift = splitbins(index) # Don't forget to update CharInfo in Unicode.h if you need to change this @@ -682,6 +734,43 @@ def make_unicode_file(version, dump(folding_index2, 'folding_index2', data_file) data_file.write('\n') + # If the following assert fails, it means space character is added to + # non-BMP area. In that case the following code should be uncommented + # and the corresponding code should be added to frontend. + assert len(non_bmp_space_set.keys()) == 0 + + data_file.write("""\ +bool +js::unicode::IsIdentifierStartNonBMP(uint32_t codePoint) +{ +""") + + for (from_code, to_code) in for_each_non_bmp_group(non_bmp_id_start_set): + data_file.write("""\ + if (codePoint >= 0x{:x} && codePoint <= 0x{:x}) + return true; +""".format(from_code, to_code)) + + data_file.write("""\ + return false; +} + +bool +js::unicode::IsIdentifierPartNonBMP(uint32_t codePoint) +{ +""") + + for (from_code, to_code) in for_each_non_bmp_group(non_bmp_id_cont_set): + data_file.write("""\ + if (codePoint >= 0x{:x} && codePoint <= 0x{:x}) + return true; +""".format(from_code, to_code)) + + data_file.write("""\ + return false; +} +""") + def getsize(data): """ return smallest possible integer size for the given array """ maxdata = max(data) @@ -1000,6 +1089,8 @@ def update_unicode(args): table, index, same_upper_table, same_upper_index, non_bmp_lower_map, non_bmp_upper_map, + non_bmp_space_set, + non_bmp_id_start_set, non_bmp_id_cont_set, test_table, test_space_table ) = process_unicode_data(unicode_data, derived_core_properties) ( @@ -1012,7 +1103,9 @@ def update_unicode(args): make_unicode_file(unicode_version, table, index, same_upper_table, same_upper_index, - folding_table, folding_index) + folding_table, folding_index, + non_bmp_space_set, + non_bmp_id_start_set, non_bmp_id_cont_set) make_non_bmp_file(unicode_version, non_bmp_lower_map, non_bmp_upper_map, non_bmp_folding_map, non_bmp_rev_folding_map) -- cgit v1.2.3 From cf70142093eb145b8e349bd7a95e22a55662a6d3 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 10:25:44 +0100 Subject: Bug 1323868: Report object allocation failure when running off-main-thread Issue #77 [Depends on] Bug 1192038: RegExp.prototype should be an ordinary object --- js/src/gc/Allocator.cpp | 8 ++++++-- js/src/jit-test/tests/gc/bug-1323868.js | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 js/src/jit-test/tests/gc/bug-1323868.js (limited to 'js/src') diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index 3994d5a5b..212493d86 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -39,8 +39,12 @@ js::Allocate(ExclusiveContext* cx, AllocKind kind, size_t nDynamicSlots, Initial MOZ_ASSERT_IF(nDynamicSlots != 0, clasp->isNative() || clasp->isProxy()); // Off-main-thread alloc cannot trigger GC or make runtime assertions. - if (!cx->isJSContext()) - return GCRuntime::tryNewTenuredObject(cx, kind, thingSize, nDynamicSlots); + if (!cx->isJSContext()) { + JSObject* obj = GCRuntime::tryNewTenuredObject(cx, kind, thingSize, nDynamicSlots); + if (MOZ_UNLIKELY(allowGC && !obj)) + ReportOutOfMemory(cx); + return obj; + } JSContext* ncx = cx->asJSContext(); JSRuntime* rt = ncx->runtime(); diff --git a/js/src/jit-test/tests/gc/bug-1323868.js b/js/src/jit-test/tests/gc/bug-1323868.js new file mode 100644 index 000000000..c7e8c9b08 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1323868.js @@ -0,0 +1,5 @@ +if (helperThreadCount() == 0) + quit(); +startgc(8301); +offThreadCompileScript("(({a,b,c}))"); +gcparam("maxBytes", gcparam("gcBytes")); -- cgit v1.2.3 From df313c5b777daf119385beb8b12c60c33b35a2d0 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 10:42:58 +0100 Subject: Use ordinary object for RegExp prototype Issue #77 --- js/src/builtin/RegExp.cpp | 140 +++++++++++++++++++++++++++------------------ js/src/vm/RegExpObject.cpp | 13 +++++ js/src/vm/RegExpObject.h | 4 +- 3 files changed, 98 insertions(+), 59 deletions(-) (limited to 'js/src') diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 80a4bb5bd..b20f41c53 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -577,14 +577,29 @@ js::regexp_clone(JSContext* cx, unsigned argc, Value* vp) return true; } -/* ES6 draft rev32 21.2.5.4. */ +MOZ_ALWAYS_INLINE bool +IsRegExpInstanceOrPrototype(HandleValue v) +{ + if (!v.isObject()) + return false; + + return StandardProtoKeyOrNull(&v.toObject()) == JSProto_RegExp; +} + +// ES 2017 draft 21.2.5.4. MOZ_ALWAYS_INLINE bool regexp_global_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted reObj(cx, &args.thisv().toObject().as()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); - /* Steps 4-6. */ + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->global()); return true; } @@ -592,19 +607,25 @@ regexp_global_impl(JSContext* cx, const CallArgs& args) bool js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); + return CallNonGenericMethod(cx, args); } -/* ES6 draft rev32 21.2.5.5. */ +// ES 2017 draft 21.2.5.5. MOZ_ALWAYS_INLINE bool regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted reObj(cx, &args.thisv().toObject().as()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); - /* Steps 4-6. */ + // Step 3.a + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->ignoreCase()); return true; } @@ -612,19 +633,25 @@ regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args) bool js::regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); + return CallNonGenericMethod(cx, args); } -/* ES6 draft rev32 21.2.5.7. */ +// ES 2017 draft 21.2.5.7. MOZ_ALWAYS_INLINE bool regexp_multiline_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted reObj(cx, &args.thisv().toObject().as()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); - /* Steps 4-6. */ + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->multiline()); return true; } @@ -632,24 +659,30 @@ regexp_multiline_impl(JSContext* cx, const CallArgs& args) bool js::regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); + return CallNonGenericMethod(cx, args); } -/* ES6 draft rev32 21.2.5.10. */ +// ES 2017 draft rev32 21.2.5.10. MOZ_ALWAYS_INLINE bool regexp_source_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted reObj(cx, &args.thisv().toObject().as()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); + + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setString(cx->names().emptyRegExp); + return true; + } - /* Step 5. */ + // Step 5. + Rooted reObj(cx, &args.thisv().toObject().as()); RootedAtom src(cx, reObj->getSource()); if (!src) return false; - /* Step 7. */ + // Step 7. RootedString str(cx, EscapeRegExpPattern(cx, src)); if (!str) return false; @@ -661,19 +694,25 @@ regexp_source_impl(JSContext* cx, const CallArgs& args) static bool regexp_source(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-4. */ + // Steps 1-4. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); + return CallNonGenericMethod(cx, args); } -/* ES6 draft rev32 21.2.5.12. */ +// ES 2017 draft 21.2.5.12. MOZ_ALWAYS_INLINE bool regexp_sticky_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted reObj(cx, &args.thisv().toObject().as()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); - /* Steps 4-6. */ + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted reObj(cx, &args.thisv().toObject().as()); args.rval().setBoolean(reObj->sticky()); return true; } @@ -681,27 +720,35 @@ regexp_sticky_impl(JSContext* cx, const CallArgs& args) bool js::regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); + return CallNonGenericMethod(cx, args); } -/* ES6 21.2.5.15. */ +// ES 2017 draft 21.2.5.15. MOZ_ALWAYS_INLINE bool regexp_unicode_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - /* Steps 4-6. */ - args.rval().setBoolean(args.thisv().toObject().as().unicode()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); + + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted reObj(cx, &args.thisv().toObject().as()); + args.rval().setBoolean(reObj->unicode()); return true; } bool js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod(cx, args); + return CallNonGenericMethod(cx, args); } const JSPropertySpec js::regexp_properties[] = { @@ -829,25 +876,6 @@ const JSPropertySpec js::regexp_static_props[] = { JS_PS_END }; -JSObject* -js::CreateRegExpPrototype(JSContext* cx, JSProtoKey key) -{ - MOZ_ASSERT(key == JSProto_RegExp); - - Rooted proto(cx, cx->global()->createBlankPrototype(cx)); - if (!proto) - return nullptr; - proto->NativeObject::setPrivate(nullptr); - - if (!RegExpObject::assignInitialShape(cx, proto)) - return nullptr; - - RootedAtom source(cx, cx->names().empty); - proto->initAndZeroLastIndex(source, RegExpFlag(0), cx); - - return proto; -} - template static bool IsTrailSurrogateWithLeadSurrogateImpl(JSContext* cx, HandleLinearString input, size_t index) diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 97f1163aa..e0b44e1eb 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -196,6 +196,12 @@ RegExpObject::trace(JSTracer* trc, JSObject* obj) } } +static JSObject* +CreateRegExpPrototype(JSContext* cx, JSProtoKey key) +{ + return cx->global()->createBlankPrototype(cx, &RegExpObject::protoClass_); +} + static const ClassOps RegExpObjectClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ @@ -229,6 +235,13 @@ const Class RegExpObject::class_ = { &RegExpObjectClassSpec }; +const Class RegExpObject::protoClass_ = { + js_Object_str, + JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp), + JS_NULL_CLASS_OPS, + &RegExpObjectClassSpec +}; + RegExpObject* RegExpObject::create(ExclusiveContext* cx, const char16_t* chars, size_t length, RegExpFlag flags, TokenStream* tokenStream, LifoAlloc& alloc) diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index d6dde1668..dc428a973 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -79,9 +79,6 @@ RegExpAlloc(ExclusiveContext* cx, HandleObject proto = nullptr); extern JSObject* CloneRegExpObject(JSContext* cx, JSObject* regexp); -extern JSObject* -CreateRegExpPrototype(JSContext* cx, JSProtoKey key); - /* * A RegExpShared is the compiled representation of a regexp. A RegExpShared is * potentially pointed to by multiple RegExpObjects. Additionally, C++ code may @@ -411,6 +408,7 @@ class RegExpObject : public NativeObject static const unsigned PRIVATE_SLOT = 3; static const Class class_; + static const Class protoClass_; // The maximum number of pairs a MatchResult can have, without having to // allocate a bigger MatchResult. -- cgit v1.2.3 From 9384b08d877055a806bd61b2a3632897dfe4bbd8 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 10:46:14 +0100 Subject: Tests Issue #77 --- .../instance-property-storage-introspection.js | 5 +--- js/src/tests/ecma_6/RegExp/prototype.js | 31 ++++++++++++++++++++++ js/src/tests/js1_8_5/extensions/clone-regexp.js | 1 - 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 js/src/tests/ecma_6/RegExp/prototype.js (limited to 'js/src') diff --git a/js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js b/js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js index 1f7c7042f..998d25e2c 100644 --- a/js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js +++ b/js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js @@ -40,9 +40,7 @@ function checkDataProperty(obj, p, expect, msg) // Check a bunch of "empty" regular expressions first. -var choices = [{ msg: "RegExp.prototype", - get: function() { return RegExp.prototype; } }, - { msg: "new RegExp()", +var choices = [{ msg: "new RegExp()", get: function() { return new RegExp(); } }, { msg: "/(?:)/", get: Function("return /(?:)/;") }]; @@ -55,7 +53,6 @@ function checkRegExp(r, msg, lastIndex) checkDataProperty(r, "lastIndex", expect, msg); } -checkRegExp(RegExp.prototype, "RegExp.prototype", 0); checkRegExp(new RegExp(), "new RegExp()", 0); checkRegExp(/(?:)/, "/(?:)/", 0); checkRegExp(Function("return /(?:)/;")(), 'Function("return /(?:)/;")()', 0); diff --git a/js/src/tests/ecma_6/RegExp/prototype.js b/js/src/tests/ecma_6/RegExp/prototype.js new file mode 100644 index 000000000..528142ab0 --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/prototype.js @@ -0,0 +1,31 @@ +const t = RegExp.prototype; + +const properties = "toSource,toString,compile,exec,test," + + "flags,global,ignoreCase,multiline,source,sticky,unicode," + + "constructor," + + "Symbol(Symbol.match),Symbol(Symbol.replace),Symbol(Symbol.search),Symbol(Symbol.split)"; +assertEq(Reflect.ownKeys(t).map(String).toString(), properties); + + +// Invoking getters on the prototype should not throw +function getter(name) { + return Object.getOwnPropertyDescriptor(t, name).get.call(t); +} + +assertEq(getter("flags"), ""); +assertEq(getter("global"), undefined); +assertEq(getter("ignoreCase"), undefined); +assertEq(getter("multiline"), undefined); +assertEq(getter("source"), "(?:)"); +assertEq(getter("sticky"), undefined); +assertEq(getter("unicode"), undefined); + +assertEq(t.toString(), "/(?:)/"); + +// The methods don't work with the prototype +assertThrowsInstanceOf(() => t.compile("b", "i"), TypeError); +assertThrowsInstanceOf(() => t.test("x"), TypeError); +assertThrowsInstanceOf(() => t.exec("x"), TypeError); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/js1_8_5/extensions/clone-regexp.js b/js/src/tests/js1_8_5/extensions/clone-regexp.js index 97f755785..8541dae98 100644 --- a/js/src/tests/js1_8_5/extensions/clone-regexp.js +++ b/js/src/tests/js1_8_5/extensions/clone-regexp.js @@ -22,7 +22,6 @@ function testRegExp(b, c=b) { testRegExp(RegExp("")); testRegExp(/(?:)/); testRegExp(/^(.*)$/gimy); -testRegExp(RegExp.prototype); var re = /\bx\b/gi; re.expando = true; -- cgit v1.2.3 From 75db97cb3772fc0693947ec17c5954a04cb234a8 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 14:48:24 +0100 Subject: Bug 1320388: Move JSFunction::HAS_REST to JSScript and LazyScript Issue #78 [Depends on] Bug 883377: Implement ES6 function "name" property semantics --- js/src/builtin/ReflectParse.cpp | 2 +- js/src/builtin/TypedObject.cpp | 2 +- js/src/frontend/BytecodeEmitter.cpp | 6 +++--- js/src/frontend/Parser.cpp | 8 ++++---- js/src/frontend/SharedContext.h | 8 +++++++- js/src/jsapi.cpp | 5 +---- js/src/jsapi.h | 6 +----- js/src/jsfun.cpp | 5 ++--- js/src/jsfun.h | 9 +-------- js/src/jsscript.cpp | 11 ++++++++++- js/src/jsscript.h | 17 +++++++++++++++++ js/src/jsstr.cpp | 2 +- js/src/vm/Interpreter.cpp | 2 -- js/src/vm/SelfHosting.cpp | 2 +- js/src/vm/Stack.cpp | 2 +- js/src/wasm/AsmJS.cpp | 2 +- 16 files changed, 52 insertions(+), 37 deletions(-) (limited to 'js/src') diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 748ff7351..e150ed729 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -3423,7 +3423,7 @@ ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst) NodeVector defaults(cx); RootedValue body(cx), rest(cx); - if (func->hasRest()) + if (pn->pn_funbox->hasRest()) rest.setUndefined(); else rest.setNull(); diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index b7297c894..ae74f01bf 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -230,7 +230,7 @@ const Class js::ScalarTypeDescr::class_ = { const JSFunctionSpec js::ScalarTypeDescr::typeObjectMethods[] = { JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0), - JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, JSFUN_HAS_REST), + JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0), JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0), JS_FS_END }; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 1e9d8f224..6ceb3ed7a 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1053,7 +1053,7 @@ BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* if (p) { MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter); MOZ_ASSERT(!funbox->hasDestructuringArgs); - MOZ_ASSERT(!funbox->function()->hasRest()); + MOZ_ASSERT(!funbox->hasRest()); p->value() = loc; continue; } @@ -8017,7 +8017,7 @@ BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result) FunctionBox* funbox = sc->asFunctionBox(); RootedFunction fun(cx, funbox->function()); - if (!fun->hasRest()) { + if (!funbox->hasRest()) { *result = false; return true; } @@ -8960,7 +8960,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) EmitterScope* funScope = innermostEmitterScope; bool hasParameterExprs = funbox->hasParameterExprs; - bool hasRest = funbox->function()->hasRest(); + bool hasRest = funbox->hasRest(); uint16_t argSlot = 0; for (ParseNode* arg = pn->pn_head; arg != funBody; arg = arg->pn_next, argSlot++) { diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index f42546eb5..78e47ceb3 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -465,6 +465,7 @@ FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* trac usesApply(false), usesThis(false), usesReturn(false), + hasRest_(false), funCxFlags() { // Functions created at parse time may be set singleton after parsing and @@ -477,7 +478,6 @@ void FunctionBox::initFromLazyFunction() { JSFunction* fun = function(); - length = fun->nargs() - fun->hasRest(); if (fun->lazyScript()->isDerivedClassConstructor()) setDerivedClassConstructor(); if (fun->lazyScript()->needsHomeObject()) @@ -492,8 +492,6 @@ FunctionBox::initStandaloneFunction(Scope* enclosingScope) // Standalone functions are Function or Generator constructors and are // always scoped to the global. MOZ_ASSERT(enclosingScope->is()); - JSFunction* fun = function(); - length = fun->nargs() - fun->hasRest(); enclosingScope_ = enclosingScope; allowNewTarget_ = true; thisBinding_ = ThisBinding::Function; @@ -2214,6 +2212,8 @@ Parser::finishFunction() lazy->setStrict(); lazy->setGeneratorKind(funbox->generatorKind()); lazy->setAsyncKind(funbox->asyncKind()); + if (funbox->hasRest()) + lazy->setHasRest(); if (funbox->isLikelyConstructorWrapper()) lazy->setLikelyConstructorWrapper(); if (funbox->isDerivedClassConstructor()) @@ -2757,7 +2757,7 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn } hasRest = true; - funbox->function()->setHasRest(); + funbox->setHasRest(); if (!tokenStream.getToken(&tt)) return false; diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 39df47c20..a6ac542f6 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -471,6 +471,7 @@ class FunctionBox : public ObjectBox, public SharedContext bool usesApply:1; /* contains an f.apply() call */ bool usesThis:1; /* contains 'this' */ bool usesReturn:1; /* contains a 'return' statement */ + bool hasRest_:1; /* has rest parameter */ FunctionContextFlags funCxFlags; @@ -539,6 +540,11 @@ class FunctionBox : public ObjectBox, public SharedContext bool isAsync() const { return asyncKind() == AsyncFunction; } bool isArrow() const { return function()->isArrow(); } + bool hasRest() const { return hasRest_; } + void setHasRest() { + hasRest_ = true; + } + void setGeneratorKind(GeneratorKind kind) { // A generator kind can be set at initialization, or when "yield" is // first seen. In both cases the transition can only happen from @@ -567,7 +573,7 @@ class FunctionBox : public ObjectBox, public SharedContext void setHasInnerFunctions() { funCxFlags.hasInnerFunctions = true; } bool hasSimpleParameterList() const { - return !function()->hasRest() && !hasParameterExprs && !hasDestructuringArgs; + return !hasRest() && !hasParameterExprs && !hasDestructuringArgs; } bool hasMappedArgsObj() const { diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index e6fc1f98b..bbf467808 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -3435,10 +3435,7 @@ JS::NewFunctionFromSpec(JSContext* cx, const JSFunctionSpec* fs, HandleId id) { return nullptr; } - JSFunction* fun = &funVal.toObject().as(); - if (fs->flags & JSFUN_HAS_REST) - fun->setHasRest(); - return fun; + return &funVal.toObject().as(); } RootedAtom atom(cx, IdToFunctionName(cx, id)); diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 332ce8562..cbef0f8fb 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -875,11 +875,7 @@ class MOZ_STACK_CLASS SourceBufferHolder final #define JSFUN_CONSTRUCTOR 0x400 /* native that can be called as a ctor */ -// 0x800 /* Unused */ - -#define JSFUN_HAS_REST 0x1000 /* function has ...rest parameter. */ - -#define JSFUN_FLAGS_MASK 0x1e00 /* | of all the JSFUN_* flags */ +#define JSFUN_FLAGS_MASK 0x600 /* | of all the JSFUN_* flags */ /* * If set, will allow redefining a non-configurable property, but only on a diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 1e1b76d5d..2359e28a2 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1329,8 +1329,7 @@ JSFunction::getLength(JSContext* cx, uint16_t* length) if (self->isInterpretedLazy() && !self->getOrCreateScript(cx)) return false; - *length = self->hasScript() ? self->nonLazyScript()->funLength() - : (self->nargs() - self->hasRest()); + *length = self->isNative() ? self->nargs() : self->nonLazyScript()->funLength(); return true; } @@ -1620,7 +1619,7 @@ const JSFunctionSpec js::function_methods[] = { JS_FN(js_apply_str, fun_apply, 2,0), JS_FN(js_call_str, fun_call, 1,0), JS_FN("isGenerator", fun_isGenerator,0,0), - JS_SELF_HOSTED_FN("bind", "FunctionBind", 2, JSFUN_HAS_REST), + JS_SELF_HOSTED_FN("bind", "FunctionBind", 2, 0), JS_SYM_FN(hasInstance, fun_symbolHasInstance, 1, JSPROP_READONLY | JSPROP_PERMANENT), JS_FS_END }; diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 88af5c22d..65da5e06f 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -61,7 +61,6 @@ class JSFunction : public js::NativeObject function-statement) */ SELF_HOSTED = 0x0080, /* function is self-hosted builtin and must not be decompilable nor constructible. */ - HAS_REST = 0x0100, /* function has a rest (...) parameter */ INTERPRETED_LAZY = 0x0200, /* function is interpreted but doesn't have a script yet */ RESOLVED_LENGTH = 0x0400, /* f.length has been resolved (see fun_resolve). */ RESOLVED_NAME = 0x0800, /* f.name has been resolved (see fun_resolve). */ @@ -95,7 +94,7 @@ class JSFunction : public js::NativeObject NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME, STABLE_ACROSS_CLONES = CONSTRUCTOR | EXPR_BODY | HAS_GUESSED_ATOM | LAMBDA | - SELF_HOSTED | HAS_REST | FUNCTION_KIND_MASK + SELF_HOSTED | FUNCTION_KIND_MASK }; static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS, @@ -183,7 +182,6 @@ class JSFunction : public js::NativeObject bool hasGuessedAtom() const { return flags() & HAS_GUESSED_ATOM; } bool isLambda() const { return flags() & LAMBDA; } bool isBoundFunction() const { return flags() & BOUND_FUN; } - bool hasRest() const { return flags() & HAS_REST; } bool isInterpretedLazy() const { return flags() & INTERPRETED_LAZY; } bool hasScript() const { return flags() & INTERPRETED; } @@ -264,11 +262,6 @@ class JSFunction : public js::NativeObject this->nargs_ = nargs; } - // Can be called multiple times by the parser. - void setHasRest() { - flags_ |= HAS_REST; - } - void setIsBoundFunction() { MOZ_ASSERT(!isBoundFunction()); flags_ |= BOUND_FUN; diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index b568b4b30..929251d8b 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -316,6 +316,7 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip IsLegacyGenerator, IsStarGenerator, IsAsync, + HasRest, OwnSource, ExplicitUseStrict, SelfHosted, @@ -431,6 +432,8 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip scriptBits |= (1 << IsStarGenerator); if (script->asyncKind() == AsyncFunction) scriptBits |= (1 << IsAsync); + if (script->hasRest()) + scriptBits |= (1 << HasRest); if (script->hasSingletons()) scriptBits |= (1 << HasSingleton); if (script->treatAsRunOnce()) @@ -582,6 +585,8 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, HandleScrip if (scriptBits & (1 << IsAsync)) script->setAsyncKind(AsyncFunction); + if (scriptBits & (1 << HasRest)) + script->setHasRest(); } JS_STATIC_ASSERT(sizeof(jsbytecode) == 1); @@ -2637,6 +2642,8 @@ JSScript::initFromFunctionBox(ExclusiveContext* cx, HandleScript script, script->isGeneratorExp_ = funbox->isGenexpLambda; script->setGeneratorKind(funbox->generatorKind()); script->setAsyncKind(funbox->asyncKind()); + if (funbox->hasRest()) + script->setHasRest(); PositionalFormalParameterIter fi(script); while (fi && !fi.closedOver()) @@ -3295,6 +3302,7 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, dst->needsHomeObject_ = src->needsHomeObject(); dst->isDefaultClassConstructor_ = src->isDefaultClassConstructor(); dst->isAsync_ = src->asyncKind() == AsyncFunction; + dst->hasRest_ = src->hasRest_; if (nconsts != 0) { GCPtrValue* vector = Rebase(dst, src, src->consts()->vector); @@ -4028,6 +4036,7 @@ LazyScript::Create(ExclusiveContext* cx, HandleFunction fun, p.shouldDeclareArguments = false; p.hasThisBinding = false; p.isAsync = false; + p.hasRest = false; p.numClosedOverBindings = closedOverBindings.length(); p.numInnerFunctions = innerFunctions.length(); p.generatorKindBits = GeneratorKindAsBits(NotGenerator); @@ -4169,7 +4178,7 @@ JSScript::hasLoops() bool JSScript::mayReadFrameArgsDirectly() { - return argumentsHasVarBinding() || (function() && function()->hasRest()); + return argumentsHasVarBinding() || hasRest(); } static inline void diff --git a/js/src/jsscript.h b/js/src/jsscript.h index ffd4b1e2e..690bc225d 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1012,6 +1012,8 @@ class JSScript : public js::gc::TenuredCell bool isAsync_:1; + bool hasRest_:1; + // Add padding so JSScript is gc::Cell aligned. Make padding protected // instead of private to suppress -Wunused-private-field compiler warnings. protected: @@ -1308,6 +1310,13 @@ class JSScript : public js::gc::TenuredCell isAsync_ = kind == js::AsyncFunction; } + bool hasRest() const { + return hasRest_; + } + void setHasRest() { + hasRest_ = true; + } + void setNeedsHomeObject() { needsHomeObject_ = true; } @@ -1940,6 +1949,7 @@ class LazyScript : public gc::TenuredCell uint32_t treatAsRunOnce : 1; uint32_t isDerivedClassConstructor : 1; uint32_t needsHomeObject : 1; + uint32_t hasRest : 1; }; union { @@ -2068,6 +2078,13 @@ class LazyScript : public gc::TenuredCell p_.isAsync = kind == AsyncFunction; } + bool hasRest() const { + return p_.hasRest; + } + void setHasRest() { + p_.hasRest = true; + } + bool strict() const { return p_.strict; } diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 32a85dc13..7adeed620 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -2894,7 +2894,7 @@ static const JSFunctionSpec string_static_methods[] = { JS_INLINABLE_FN("fromCharCode", js::str_fromCharCode, 1, 0, StringFromCharCode), JS_INLINABLE_FN("fromCodePoint", js::str_fromCodePoint, 1, 0, StringFromCodePoint), - JS_SELF_HOSTED_FN("raw", "String_static_raw", 2,JSFUN_HAS_REST), + JS_SELF_HOSTED_FN("raw", "String_static_raw", 2,0), JS_SELF_HOSTED_FN("substring", "String_static_substring", 3,0), JS_SELF_HOSTED_FN("substr", "String_static_substr", 3,0), JS_SELF_HOSTED_FN("slice", "String_static_slice", 3,0), diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index fbf526ae5..51e809345 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -282,8 +282,6 @@ MakeDefaultConstructor(JSContext* cx, JSOp op, JSAtom* atom, HandleObject proto) ctor->setIsConstructor(); ctor->setIsClassConstructor(); - if (derived) - ctor->setHasRest(); MOZ_ASSERT(ctor->infallibleIsDefaultClassConstructor(cx)); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 6737e774c..fd604c6bf 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -3022,7 +3022,7 @@ JSRuntime::cloneSelfHostedFunctionScript(JSContext* cx, HandlePropertyName name, MOZ_ASSERT(!targetFun->isInterpretedLazy()); MOZ_ASSERT(sourceFun->nargs() == targetFun->nargs()); - MOZ_ASSERT(sourceFun->hasRest() == targetFun->hasRest()); + MOZ_ASSERT(sourceScript->hasRest() == targetFun->nonLazyScript()->hasRest()); // The target function might have been relazified after its flags changed. targetFun->setFlags(targetFun->flags() | sourceFun->flags()); diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 439bb1ed4..87e95c893 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -85,7 +85,7 @@ InterpreterFrame::isNonGlobalEvalFrame() const JSObject* InterpreterFrame::createRestParameter(JSContext* cx) { - MOZ_ASSERT(callee().hasRest()); + MOZ_ASSERT(script()->hasRest()); unsigned nformal = callee().nargs() - 1, nactual = numActualArgs(); unsigned nrest = (nactual > nformal) ? nactual - nformal : 0; Value* restvp = argv() + nformal; diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 4dbc9b387..d2f331bf1 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -3249,7 +3249,7 @@ static bool CheckFunctionHead(ModuleValidator& m, ParseNode* fn) { JSFunction* fun = FunctionObject(fn); - if (fun->hasRest()) + if (fn->pn_funbox->hasRest()) return m.fail(fn, "rest args not allowed"); if (fun->isExprBody()) return m.fail(fn, "expression closures not allowed"); -- cgit v1.2.3 From 213f9ea384c71eac84667d65a21dc96e422798db Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 14:55:56 +0100 Subject: Bug 1320042: Rename BytecodeEmitter::emitConditionallyExecuted{SOMETHING} to BytecodeEmitter::emit{SOMETHING}InBranch Issue #78 [Depends on] Bug 883377: Implement ES6 function "name" property semantics --- js/src/frontend/BytecodeEmitter.cpp | 32 ++++++++++++++++---------------- js/src/frontend/BytecodeEmitter.h | 5 ++--- 2 files changed, 18 insertions(+), 19 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 6ceb3ed7a..0a1b4ffe1 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4463,7 +4463,7 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla } bool -BytecodeEmitter::emitConditionallyExecutedDestructuringLHS(ParseNode* target, DestructuringFlavor flav) +BytecodeEmitter::emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav) { TDZCheckCache tdzCache(this); return emitDestructuringLHS(target, flav); @@ -4507,7 +4507,7 @@ BytecodeEmitter::emitDefault(ParseNode* defaultExpr) return false; if (!emit1(JSOP_POP)) // . return false; - if (!emitConditionallyExecutedTree(defaultExpr)) // DEFAULTVALUE + if (!emitTreeInBranch(defaultExpr)) // DEFAULTVALUE return false; if (!emitJumpTargetAndPatch(jump)) return false; @@ -4765,7 +4765,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav return false; if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY return false; - if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ? + if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? return false; if (!ifThenElse.emitElse()) // ... OBJ? ITER @@ -4782,7 +4782,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav return false; if (!emit1(JSOP_POP)) // ... OBJ? ARRAY return false; - if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ? + if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? return false; if (!isHead) { @@ -4834,7 +4834,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav if (pndefault) { // Emit only pndefault tree here, as undefined check in emitDefault // should always be true. - if (!emitConditionallyExecutedTree(pndefault)) // ... OBJ? ITER VALUE + if (!emitTreeInBranch(pndefault)) // ... OBJ? ITER VALUE return false; } else { if (!isElision) { @@ -4845,7 +4845,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } } if (!isElision) { - if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER + if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER return false; } else if (pndefault) { if (!emit1(JSOP_POP)) // ... OBJ? ITER @@ -4877,7 +4877,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } if (!isElision) { - if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER + if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER return false; } else { if (!emit1(JSOP_POP)) // ... OBJ? ITER @@ -5788,7 +5788,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) if_again: /* Emit code for the condition before pushing stmtInfo. */ - if (!emitConditionallyExecutedTree(pn->pn_kid1)) + if (!emitTreeInBranch(pn->pn_kid1)) return false; ParseNode* elseNode = pn->pn_kid3; @@ -5801,7 +5801,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) } /* Emit code for the then part. */ - if (!emitConditionallyExecutedTree(pn->pn_kid2)) + if (!emitTreeInBranch(pn->pn_kid2)) return false; if (elseNode) { @@ -5814,7 +5814,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) } /* Emit code for the else part. */ - if (!emitConditionallyExecutedTree(elseNode)) + if (!emitTreeInBranch(elseNode)) return false; } @@ -6504,7 +6504,7 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterSc if (jmp.offset == -1 && !emitLoopEntry(forBody, jmp)) return false; - if (!emitConditionallyExecutedTree(forBody)) + if (!emitTreeInBranch(forBody)) return false; // Set loop and enclosing "update" offsets, for continue. Note that we @@ -7284,7 +7284,7 @@ BytecodeEmitter::emitWhile(ParseNode* pn) if (!emitLoopHead(pn->pn_right, &top)) return false; - if (!emitConditionallyExecutedTree(pn->pn_right)) + if (!emitTreeInBranch(pn->pn_right)) return false; if (!emitLoopEntry(pn->pn_left, jmp)) @@ -8449,13 +8449,13 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional) if (!ifThenElse.emitCond()) return false; - if (!emitConditionallyExecutedTree(&conditional.thenExpression())) + if (!emitTreeInBranch(&conditional.thenExpression())) return false; if (!ifThenElse.emitElse()) return false; - if (!emitConditionallyExecutedTree(&conditional.elseExpression())) + if (!emitTreeInBranch(&conditional.elseExpression())) return false; if (!ifThenElse.emitEnd()) @@ -9017,7 +9017,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) return false; if (!emit1(JSOP_POP)) return false; - if (!emitConditionallyExecutedTree(initializer)) + if (!emitTreeInBranch(initializer)) return false; if (!emitJumpTargetAndPatch(jump)) return false; @@ -9728,7 +9728,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) } bool -BytecodeEmitter::emitConditionallyExecutedTree(ParseNode* pn) +BytecodeEmitter::emitTreeInBranch(ParseNode* pn) { // Code that may be conditionally executed always need their own TDZ // cache. diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 1bb4191ee..08e0eb54f 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -435,7 +435,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); // Emit code for the tree rooted at pn with its own TDZ cache. - MOZ_MUST_USE bool emitConditionallyExecutedTree(ParseNode* pn); + MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn); // Emit global, eval, or module code for tree rooted at body. Always // encompasses the entire source. @@ -648,8 +648,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter // the stack and emits code to destructure a single lhs expression (either a // name or a compound []/{} expression). MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav); - MOZ_MUST_USE bool emitConditionallyExecutedDestructuringLHS(ParseNode* target, - DestructuringFlavor flav); + MOZ_MUST_USE bool emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or -- cgit v1.2.3 From 6822460d3b0d4609ee5d4e1ab4b093799ed06580 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 15:18:37 +0100 Subject: Bug 1317309: Throw a TypeError when passing a Symbol value to ToAtom Issue #78 [Depends on] Bug 883377: Implement ES6 function "name" property semantics --- js/src/jsatom.cpp | 8 ++++++++ js/src/jsfun.cpp | 12 +++++++++--- js/src/tests/ecma_6/RegExp/compile-symbol.js | 14 ++++++++++++++ js/src/tests/ecma_6/RegExp/constructor-symbol.js | 14 ++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 js/src/tests/ecma_6/RegExp/compile-symbol.js create mode 100644 js/src/tests/ecma_6/RegExp/constructor-symbol.js (limited to 'js/src') diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 3f8e8d8f8..2a3c58638 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -510,6 +510,14 @@ ToAtomSlow(ExclusiveContext* cx, typename MaybeRooted::HandleTyp return v.toBoolean() ? cx->names().true_ : cx->names().false_; if (v.isNull()) return cx->names().null; + if (v.isSymbol()) { + if (cx->shouldBeJSContext() && allowGC) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_SYMBOL_TO_STRING); + } + return nullptr; + } + MOZ_ASSERT(v.isUndefined()); return cx->names().undefined; } diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 2359e28a2..1d44f0ea0 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -2131,9 +2131,10 @@ js::CloneFunctionAndScript(JSContext* cx, HandleFunction fun, HandleObject enclo * * Function names are always strings. If id is the well-known @@iterator * symbol, this returns "[Symbol.iterator]". If a prefix is supplied the final - * name is |prefix + " " + name|. + * name is |prefix + " " + name|. A prefix cannot be supplied if id is a + * symbol value. * - * Implements step 4 and 5 of SetFunctionName in ES 2016 draft Dec 20, 2015. + * Implements steps 3-5 of 9.2.11 SetFunctionName in ES2016. */ JSAtom* js::IdToFunctionName(JSContext* cx, HandleId id, const char* prefix /* = nullptr */) @@ -2141,7 +2142,11 @@ js::IdToFunctionName(JSContext* cx, HandleId id, const char* prefix /* = nullptr if (JSID_IS_ATOM(id) && !prefix) return JSID_TO_ATOM(id); - if (JSID_IS_SYMBOL(id) && !prefix) { + // Step 3. + MOZ_ASSERT_IF(prefix, !JSID_IS_SYMBOL(id)); + + // Step 4. + if (JSID_IS_SYMBOL(id)) { RootedAtom desc(cx, JSID_TO_SYMBOL(id)->description()); StringBuffer sb(cx); if (!sb.append('[') || !sb.append(desc) || !sb.append(']')) @@ -2149,6 +2154,7 @@ js::IdToFunctionName(JSContext* cx, HandleId id, const char* prefix /* = nullptr return sb.finishAtom(); } + // Step 5. RootedValue idv(cx, IdToValue(id)); if (!prefix) return ToAtom(cx, idv); diff --git a/js/src/tests/ecma_6/RegExp/compile-symbol.js b/js/src/tests/ecma_6/RegExp/compile-symbol.js new file mode 100644 index 000000000..9eea1124c --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/compile-symbol.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +for (let sym of [Symbol.iterator, Symbol(), Symbol("description")]) { + let re = /a/; + + assertEq(re.source, "a"); + assertThrowsInstanceOf(() => re.compile(sym), TypeError); + assertEq(re.source, "a"); +} + +if (typeof reportCompare === 'function') + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/RegExp/constructor-symbol.js b/js/src/tests/ecma_6/RegExp/constructor-symbol.js new file mode 100644 index 000000000..503d7e5a8 --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/constructor-symbol.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +for (let sym of [Symbol.iterator, Symbol(), Symbol("description")]) { + assertThrowsInstanceOf(() => RegExp(sym), TypeError); + assertThrowsInstanceOf(() => new RegExp(sym), TypeError); + + assertThrowsInstanceOf(() => RegExp(sym, "g"), TypeError); + assertThrowsInstanceOf(() => new RegExp(sym, "g"), TypeError); +} + +if (typeof reportCompare === 'function') + reportCompare(0, 0); -- cgit v1.2.3 From 5ef44cf6484b9dfd49c0174ac2969a29587a1bbd Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 15:47:10 +0100 Subject: Part 1: Implement ES6 function name property semantics Issue #78 --- js/src/builtin/ModuleObject.cpp | 2 +- js/src/builtin/ReflectParse.cpp | 2 +- js/src/frontend/BytecodeEmitter.cpp | 111 ++++++++++++++-- js/src/frontend/BytecodeEmitter.h | 11 +- js/src/frontend/FullParseHandler.h | 7 ++ js/src/frontend/ParseNode-inl.h | 2 +- js/src/frontend/ParseNode.cpp | 18 +++ js/src/frontend/ParseNode.h | 15 +++ js/src/frontend/Parser.cpp | 35 ++++-- js/src/frontend/SyntaxParseHandler.h | 3 + js/src/jsapi.cpp | 6 +- js/src/jscntxt.cpp | 2 +- js/src/jsfun.cpp | 109 +++++++++++++--- js/src/jsfun.h | 54 ++++++-- js/src/jsfuninlines.h | 2 +- .../ecma_6/Function/function-name-assignment.js | 139 +++++++++++++++++++++ .../tests/ecma_6/Function/function-name-binding.js | 54 ++++++++ .../tests/ecma_6/Function/function-name-class.js | 32 +++++ js/src/tests/ecma_6/Function/function-name-for.js | 31 +++++ .../tests/ecma_6/Function/function-name-method.js | 70 +++++++++++ .../ecma_6/Function/function-name-property.js | 58 +++++++++ js/src/vm/AsyncFunction.cpp | 5 +- js/src/vm/Debugger.cpp | 2 +- js/src/vm/GlobalObject.cpp | 4 +- js/src/vm/Interpreter.cpp | 22 +++- js/src/vm/Opcodes.h | 11 +- js/src/vm/SelfHosting.cpp | 8 +- js/src/vm/TypeInference.cpp | 2 +- js/src/vm/TypedArrayObject.cpp | 2 +- js/src/wasm/AsmJS.cpp | 12 +- 30 files changed, 750 insertions(+), 81 deletions(-) create mode 100644 js/src/tests/ecma_6/Function/function-name-assignment.js create mode 100644 js/src/tests/ecma_6/Function/function-name-binding.js create mode 100644 js/src/tests/ecma_6/Function/function-name-class.js create mode 100644 js/src/tests/ecma_6/Function/function-name-for.js create mode 100644 js/src/tests/ecma_6/Function/function-name-method.js create mode 100644 js/src/tests/ecma_6/Function/function-name-property.js (limited to 'js/src') diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 3bfc8f60b..40fd008b9 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -1205,7 +1205,7 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) case PNK_FUNCTION: { RootedFunction func(cx_, kid->pn_funbox->function()); if (!func->isArrow()) { - RootedAtom localName(cx_, func->name()); + RootedAtom localName(cx_, func->explicitName()); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); MOZ_ASSERT_IF(isDefault, localName); if (!appendExportEntry(exportName, localName)) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index e150ed729..eef6fd7ec 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -3415,7 +3415,7 @@ ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst) #endif RootedValue id(cx); - RootedAtom funcAtom(cx, func->name()); + RootedAtom funcAtom(cx, func->explicitName()); if (!optIdentifier(funcAtom, nullptr, &id)) return false; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 0a1b4ffe1..205bbf3d9 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4070,7 +4070,7 @@ BytecodeEmitter::isRunOnceLambda() FunctionBox* funbox = sc->asFunctionBox(); return !funbox->argumentsHasLocalBinding() && !funbox->isGenerator() && - !funbox->function()->name(); + !funbox->function()->explicitName(); } bool @@ -4491,7 +4491,7 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) } bool -BytecodeEmitter::emitDefault(ParseNode* defaultExpr) +BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { if (!emit1(JSOP_DUP)) // VALUE VALUE return false; @@ -4507,13 +4507,79 @@ BytecodeEmitter::emitDefault(ParseNode* defaultExpr) return false; if (!emit1(JSOP_POP)) // . return false; - if (!emitTreeInBranch(defaultExpr)) // DEFAULTVALUE + if (!emitInitializerInBranch(defaultExpr, pattern)) // DEFAULTVALUE return false; if (!emitJumpTargetAndPatch(jump)) return false; return true; } +bool +BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, + FunctionPrefixKind prefixKind) +{ + if (maybeFun->isKind(PNK_FUNCTION)) { + // Function doesn't have 'name' property at this point. + // Set function's name at compile time. + RootedFunction fun(cx, maybeFun->pn_funbox->function()); + + // Single node can be emitted multiple times if it appears in + // array destructuring default. If function already has a name, + // just return. + if (fun->hasCompileTimeName()) { +#ifdef DEBUG + RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + if (!funName) + return false; + MOZ_ASSERT(funName == maybeFun->pn_funbox->function()->compileTimeName()); +#endif + return true; + } + + RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + if (!funName) + return false; + if (fun->hasGuessedAtom()) + fun->clearGuessedAtom(); + fun->setCompileTimeName(name); + return true; + } + + uint32_t nameIndex; + if (!makeAtomIndex(name, &nameIndex)) + return false; + if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME + return false; + uint8_t kind = uint8_t(prefixKind); + if (!emit2(JSOP_SETFUNNAME, kind)) // FUN + return false; + return true; +} + +bool +BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) +{ + if (!emitTree(initializer)) + return false; + + if (!pattern->isInParens() && pattern->isKind(PNK_NAME) && + initializer->isDirectRHSAnonFunction()) + { + RootedAtom name(cx, pattern->name()); + if (!setOrEmitSetFunName(initializer, name, FunctionPrefixKind::None)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern) +{ + TDZCheckCache tdzCache(this); + return emitInitializer(initializer, pattern); +} + class MOZ_STACK_CLASS IfThenElseEmitter { BytecodeEmitter* bce_; @@ -4834,7 +4900,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav if (pndefault) { // Emit only pndefault tree here, as undefined check in emitDefault // should always be true. - if (!emitTreeInBranch(pndefault)) // ... OBJ? ITER VALUE + if (!emitInitializerInBranch(pndefault, subpattern)) // ... OBJ? ITER VALUE return false; } else { if (!isElision) { @@ -4872,7 +4938,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav return false; if (pndefault) { - if (!emitDefault(pndefault)) // ... OBJ? ITER VALUE + if (!emitDefault(pndefault, subpattern)) // ... OBJ? ITER VALUE return false; } @@ -4980,7 +5046,7 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla return false; if (subpattern->isKind(PNK_ASSIGN)) { - if (!emitDefault(subpattern->pn_right)) + if (!emitDefault(subpattern->pn_right, subpattern->pn_left)) return false; subpattern = subpattern->pn_left; } @@ -5094,7 +5160,7 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, if (!initializer && declList->isKind(PNK_VAR)) return true; - auto emitRhs = [initializer, declList](BytecodeEmitter* bce, const NameLocation&, bool) { + auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) if (!initializer) { // Lexical declarations are initialized to undefined without an // initializer. @@ -5105,7 +5171,7 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, } MOZ_ASSERT(initializer); - return bce->emitTree(initializer); + return bce->emitInitializer(initializer, decl); }; if (!emitInitializeName(decl, emitRhs)) @@ -5164,6 +5230,12 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs) if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) return false; + if (!lhs->isInParens() && op == JSOP_NOP && rhs && rhs->isDirectRHSAnonFunction()) { + RootedAtom name(bce->cx, lhs->name()); + if (!bce->setOrEmitSetFunName(rhs, name, FunctionPrefixKind::None)) + return false; + } + // Emit the compound assignment op if there is one. if (op != JSOP_NOP && !bce->emit1(op)) return false; @@ -6283,8 +6355,8 @@ BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitte if (!updateSourceCoordNotes(decl->pn_pos.begin)) return false; - auto emitRhs = [initializer](BytecodeEmitter* bce, const NameLocation&, bool) { - return bce->emitTree(initializer); + auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) { + return bce->emitInitializer(initializer, decl); }; if (!emitInitializeName(decl, emitRhs)) @@ -6903,7 +6975,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) { FunctionBox* funbox = pn->pn_funbox; RootedFunction fun(cx, funbox->function()); - RootedAtom name(cx, fun->name()); + RootedAtom name(cx, fun->explicitName()); MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto); @@ -8532,6 +8604,10 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); + FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get + : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set + : FunctionPrefixKind::None; + if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) objp.set(nullptr); @@ -8573,6 +8649,12 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; default: MOZ_CRASH("Invalid op"); } + if (propdef->pn_right->isDirectRHSAnonFunction()) { + if (!emitDupAt(1)) + return false; + if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) + return false; + } if (!emit1(op)) return false; } else { @@ -8597,6 +8679,11 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, objp.set(nullptr); } + if (propdef->pn_right->isDirectRHSAnonFunction()) { + RootedAtom keyName(cx, key->pn_atom); + if (!setOrEmitSetFunName(propdef->pn_right, keyName, prefixKind)) + return false; + } if (!emitIndex32(op, index)) return false; } @@ -9017,7 +9104,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) return false; if (!emit1(JSOP_POP)) return false; - if (!emitTreeInBranch(initializer)) + if (!emitInitializerInBranch(initializer, bindingElement)) return false; if (!emitJumpTargetAndPatch(jump)) return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 08e0eb54f..9a2ddb568 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -677,7 +677,16 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Check if the value on top of the stack is "undefined". If so, replace // that value on the stack with the value defined by |defaultExpr|. - MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr); + // |pattern| is a lhs node of the default expression. If it's an + // identifier and |defaultExpr| is an anonymous function, |SetFunctionName| + // is called at compile time. + MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); + + MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, + FunctionPrefixKind prefixKind); + + MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); + MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(ParseNode* pn); MOZ_MUST_USE bool emitTemplateString(ParseNode* pn); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index add881900..0fd137796 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -665,6 +665,11 @@ class FullParseHandler ParseNode* pn); inline void setLastFunctionFormalParameterDestructuring(ParseNode* funcpn, ParseNode* pn); + void checkAndSetIsDirectRHSAnonFunction(ParseNode* pn) { + if (IsAnonymousFunctionDefinition(pn)) + pn->setDirectRHSAnonFunction(true); + } + ParseNode* newFunctionDefinition() { return new_(PNK_FUNCTION, pos()); } @@ -942,6 +947,8 @@ FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn, Parse if (!pn) return false; + checkAndSetIsDirectRHSAnonFunction(defaultValue); + funcpn->pn_body->pn_pos.end = pn->pn_pos.end; ParseNode* pnchild = funcpn->pn_body->pn_head; ParseNode* pnlast = funcpn->pn_body->last(); diff --git a/js/src/frontend/ParseNode-inl.h b/js/src/frontend/ParseNode-inl.h index 395d09b5b..21bd83766 100644 --- a/js/src/frontend/ParseNode-inl.h +++ b/js/src/frontend/ParseNode-inl.h @@ -18,7 +18,7 @@ inline PropertyName* ParseNode::name() const { MOZ_ASSERT(isKind(PNK_FUNCTION) || isKind(PNK_NAME)); - JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->name() : pn_atom; + JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->explicitName() : pn_atom; return atom->asPropertyName(); } diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index f79baba9e..ece3a45df 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -902,3 +902,21 @@ FunctionBox::trace(JSTracer* trc) if (enclosingScope_) TraceRoot(trc, &enclosingScope_, "funbox-enclosingScope"); } + +bool +js::frontend::IsAnonymousFunctionDefinition(ParseNode* pn) +{ + // ES 2017 draft + // 12.15.2 (ArrowFunction, AsyncArrowFunction). + // 14.1.12 (FunctionExpression). + // 14.4.8 (GeneratorExpression). + // 14.6.8 (AsyncFunctionExpression) + if (pn->isKind(PNK_FUNCTION) && !pn->pn_funbox->function()->explicitName()) + return true; + + // 14.5.8 (ClassExpression) + if (pn->is() && !pn->as().names()) + return true; + + return false; +} diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index d37aaaae0..ff26279af 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -450,6 +450,9 @@ class ParseNode uint8_t pn_op; /* see JSOp enum and jsopcode.tbl */ uint8_t pn_arity:4; /* see ParseNodeArity enum */ bool pn_parens:1; /* this expr was enclosed in parens */ + bool pn_rhs_anon_fun:1; /* this expr is anonymous function or class that + * is a direct RHS of PNK_ASSIGN or PNK_COLON of + * property, that needs SetFunctionName. */ ParseNode(const ParseNode& other) = delete; void operator=(const ParseNode& other) = delete; @@ -460,6 +463,7 @@ class ParseNode pn_op(op), pn_arity(arity), pn_parens(false), + pn_rhs_anon_fun(false), pn_pos(0, 0), pn_next(nullptr) { @@ -472,6 +476,7 @@ class ParseNode pn_op(op), pn_arity(arity), pn_parens(false), + pn_rhs_anon_fun(false), pn_pos(pos), pn_next(nullptr) { @@ -512,6 +517,13 @@ class ParseNode bool isLikelyIIFE() const { return isInParens(); } void setInParens(bool enabled) { pn_parens = enabled; } + bool isDirectRHSAnonFunction() const { + return pn_rhs_anon_fun; + } + void setDirectRHSAnonFunction(bool enabled) { + pn_rhs_anon_fun = enabled; + } + TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */ ParseNode* pn_next; /* intrinsic link in parent PN_LIST */ @@ -1444,6 +1456,9 @@ FunctionFormalParametersList(ParseNode* fn, unsigned* numFormals) return argsBody->pn_head; } +bool +IsAnonymousFunctionDefinition(ParseNode* pn); + } /* namespace frontend */ } /* namespace js */ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 78e47ceb3..3106702cf 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -326,10 +326,14 @@ ParseContext::init() if (fun->isNamedLambda()) { if (!namedLambdaScope_->init(this)) return false; - AddDeclaredNamePtr p = namedLambdaScope_->lookupDeclaredNameForAdd(fun->name()); + AddDeclaredNamePtr p = + namedLambdaScope_->lookupDeclaredNameForAdd(fun->explicitName()); MOZ_ASSERT(!p); - if (!namedLambdaScope_->addDeclaredName(this, p, fun->name(), DeclarationKind::Const)) + if (!namedLambdaScope_->addDeclaredName(this, p, fun->explicitName(), + DeclarationKind::Const)) + { return false; + } } if (!functionScope_->init(this)) @@ -367,7 +371,7 @@ ParseContext::removeInnerFunctionBoxesForAnnexB(JSAtom* name) { for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) { if (FunctionBox* funbox = innerFunctionBoxesForAnnexB_[i]) { - if (funbox->function()->name() == name) + if (funbox->function()->explicitName() == name) innerFunctionBoxesForAnnexB_[i] = nullptr; } } @@ -3477,8 +3481,8 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, if (!body) return false; - if ((kind != Method && !IsConstructorKind(kind)) && fun->name()) { - RootedPropertyName propertyName(context, fun->name()->asPropertyName()); + if ((kind != Method && !IsConstructorKind(kind)) && fun->explicitName()) { + RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName()); if (!checkStrictBinding(propertyName, handler.getPosition(pn))) return false; } @@ -4337,6 +4341,8 @@ Parser::declarationPattern(Node decl, DeclarationKind declKind, To if (!init) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(init); + if (forHeadKind) { // For for(;;) declarations, consistency with |for (;| parsing requires // that the ';' first be examined as Operand, even though absence of a @@ -4366,6 +4372,8 @@ Parser::initializerInNameDeclaration(Node decl, Node binding, if (!initializer) return false; + handler.checkAndSetIsDirectRHSAnonFunction(initializer); + if (forHeadKind) { if (initialDeclaration) { bool isForIn, isForOf; @@ -5063,7 +5071,7 @@ Parser::exportDeclaration() if (!kid) return null(); - if (!checkExportedName(kid->pn_funbox->function()->name())) + if (!checkExportedName(kid->pn_funbox->function()->explicitName())) return null(); break; @@ -6670,8 +6678,6 @@ Parser::classDefinition(YieldHandling yieldHandling, return null(); } - // FIXME: Implement ES6 function "name" property semantics - // (bug 883377). RootedAtom funName(context); switch (propType) { case PropertyType::GetterNoExpressionClosure: @@ -6694,6 +6700,8 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!fn) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(fn); + JSOp op = JSOpFromPropertyType(propType); if (!handler.addClassMethodDefinition(classMethods, propName, fn, op, isStatic)) return null(); @@ -7753,6 +7761,9 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl return null(); } + if (kind == PNK_ASSIGN) + handler.checkAndSetIsDirectRHSAnonFunction(rhs); + return handler.newAssignment(kind, lhs, rhs, op); } @@ -9155,6 +9166,8 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!propExpr) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(propExpr); + if (foldConstants && !FoldConstants(context, &propExpr, this)) return null(); @@ -9268,6 +9281,8 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* return null(); } + handler.checkAndSetIsDirectRHSAnonFunction(rhs); + Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP); if (!propExpr) return null(); @@ -9278,8 +9293,6 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!abortIfSyntaxParser()) return null(); } else { - // FIXME: Implement ES6 function "name" property semantics - // (bug 883377). RootedAtom funName(context); if (!tokenStream.isCurrentTokenType(TOK_RB)) { funName = propAtom; @@ -9295,6 +9308,8 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!fn) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(fn); + JSOp op = JSOpFromPropertyType(propType); if (!handler.addObjectMethodDefinition(literal, propName, fn, op)) return null(); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 75c7e3333..b7f00605b 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -339,6 +339,9 @@ class SyntaxParseHandler Node catchGuard, Node catchBody) { return true; } MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(Node funcpn, Node pn) { return true; } + + void checkAndSetIsDirectRHSAnonFunction(Node pn) {} + Node newFunctionDefinition() { return NodeFunctionDefinition; } bool setComprehensionLambdaBody(Node pn, Node body) { return true; } void setFunctionFormalParametersAndBody(Node pn, Node kid) {} diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index bbf467808..85a38bba4 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2095,7 +2095,7 @@ DefinePropertyById(JSContext* cx, HandleObject obj, HandleId id, HandleValue val getter != JS_PropertyStub && setter != JS_StrictPropertyStub) { if (getter && !(attrs & JSPROP_GETTER)) { - RootedAtom atom(cx, IdToFunctionName(cx, id, "get")); + RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Get)); if (!atom) return false; JSFunction* getobj = NewNativeFunction(cx, (Native) getter, 0, atom); @@ -2111,7 +2111,7 @@ DefinePropertyById(JSContext* cx, HandleObject obj, HandleId id, HandleValue val if (setter && !(attrs & JSPROP_SETTER)) { // Root just the getter, since the setter is not yet a JSObject. AutoRooterGetterSetter getRoot(cx, JSPROP_GETTER, &getter, nullptr); - RootedAtom atom(cx, IdToFunctionName(cx, id, "set")); + RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Set)); if (!atom) return false; JSFunction* setobj = NewNativeFunction(cx, (Native) setter, 1, atom); @@ -3615,7 +3615,7 @@ JS_GetFunctionObject(JSFunction* fun) JS_PUBLIC_API(JSString*) JS_GetFunctionId(JSFunction* fun) { - return fun->name(); + return fun->explicitName(); } JS_PUBLIC_API(JSString*) diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 3ffd9ad7b..31d62332d 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -803,7 +803,7 @@ js::ReportMissingArg(JSContext* cx, HandleValue v, unsigned arg) SprintfLiteral(argbuf, "%u", arg); if (IsFunctionObject(v)) { - RootedAtom name(cx, v.toObject().as().name()); + RootedAtom name(cx, v.toObject().as().explicitName()); bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, name); if (!bytes) return; diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 1d44f0ea0..bcb0da80b 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -562,7 +562,7 @@ js::XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, return false; } - if (fun->name() || fun->hasGuessedAtom()) + if (fun->explicitName() || fun->hasCompileTimeName() || fun->hasGuessedAtom()) firstword |= HasAtom; if (fun->isStarGenerator()) @@ -1042,8 +1042,8 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) if (!ok) return nullptr; } - if (fun->name()) { - if (!out.append(fun->name())) + if (fun->explicitName()) { + if (!out.append(fun->explicitName())) return nullptr; } @@ -1364,14 +1364,14 @@ JSFunction::getUnresolvedName(JSContext* cx) if (isClassConstructor()) { // It's impossible to have an empty named class expression. We use // empty as a sentinel when creating default class constructors. - MOZ_ASSERT(name() != cx->names().empty); + MOZ_ASSERT(explicitOrCompileTimeName() != cx->names().empty); // Unnamed class expressions should not get a .name property at all. - return name(); + return explicitOrCompileTimeName(); } - // Returns the empty string for unnamed functions (FIXME: bug 883377). - return name() != nullptr ? name() : cx->names().empty; + return explicitOrCompileTimeName() != nullptr ? explicitOrCompileTimeName() + : cx->names().empty; } static const js::Value& @@ -2137,34 +2137,109 @@ js::CloneFunctionAndScript(JSContext* cx, HandleFunction fun, HandleObject enclo * Implements steps 3-5 of 9.2.11 SetFunctionName in ES2016. */ JSAtom* -js::IdToFunctionName(JSContext* cx, HandleId id, const char* prefix /* = nullptr */) +js::IdToFunctionName(JSContext* cx, HandleId id, + FunctionPrefixKind prefixKind /* = FunctionPrefixKind::None */) { - if (JSID_IS_ATOM(id) && !prefix) + // No prefix fastpath. + if (JSID_IS_ATOM(id) && prefixKind == FunctionPrefixKind::None) return JSID_TO_ATOM(id); - // Step 3. - MOZ_ASSERT_IF(prefix, !JSID_IS_SYMBOL(id)); + // Step 3 (implicit). // Step 4. if (JSID_IS_SYMBOL(id)) { + // Step 4.a. RootedAtom desc(cx, JSID_TO_SYMBOL(id)->description()); + + // Step 4.b, no prefix fastpath. + if (!desc && prefixKind == FunctionPrefixKind::None) + return cx->names().empty; + + // Step 5 (reordered). StringBuffer sb(cx); - if (!sb.append('[') || !sb.append(desc) || !sb.append(']')) - return nullptr; + if (prefixKind == FunctionPrefixKind::Get) { + if (!sb.append("get ")) + return nullptr; + } else if (prefixKind == FunctionPrefixKind::Set) { + if (!sb.append("set ")) + return nullptr; + } + + // Step 4.b. + if (desc) { + // Step 4.c. + if (!sb.append('[') || !sb.append(desc) || !sb.append(']')) + return nullptr; + } return sb.finishAtom(); } - // Step 5. RootedValue idv(cx, IdToValue(id)); - if (!prefix) - return ToAtom(cx, idv); + RootedAtom name(cx, ToAtom(cx, idv)); + if (!name) + return nullptr; + + // Step 5. + return NameToFunctionName(cx, name, prefixKind); +} + +JSAtom* +js::NameToFunctionName(ExclusiveContext* cx, HandleAtom name, + FunctionPrefixKind prefixKind /* = FunctionPrefixKind::None */) +{ + if (prefixKind == FunctionPrefixKind::None) + return name; StringBuffer sb(cx); - if (!sb.append(prefix, strlen(prefix)) || !sb.append(' ') || !sb.append(ToAtom(cx, idv))) + if (prefixKind == FunctionPrefixKind::Get) { + if (!sb.append("get ")) + return nullptr; + } else { + if (!sb.append("set ")) + return nullptr; + } + if (!sb.append(name)) return nullptr; return sb.finishAtom(); } +bool +js::SetFunctionNameIfNoOwnName(JSContext* cx, HandleFunction fun, HandleValue name, + FunctionPrefixKind prefixKind) +{ + MOZ_ASSERT(name.isString() || name.isSymbol() || name.isNumber()); + + if (fun->isClassConstructor()) { + // A class may have static 'name' method or accessor. + RootedId nameId(cx, NameToId(cx->names().name)); + bool result; + if (!HasOwnProperty(cx, fun, nameId, &result)) + return false; + + if (result) + return true; + } else { + // Anonymous function shouldn't have own 'name' property at this point. + MOZ_ASSERT(!fun->containsPure(cx->names().name)); + } + + RootedId id(cx); + if (!ValueToId(cx, name, &id)) + return false; + + RootedAtom funNameAtom(cx, IdToFunctionName(cx, id, prefixKind)); + if (!funNameAtom) + return false; + + RootedValue funNameVal(cx, StringValue(funNameAtom)); + if (!NativeDefineProperty(cx, fun, cx->names().name, funNameVal, nullptr, nullptr, + JSPROP_READONLY)) + { + return false; + } + return true; +} + JSFunction* js::DefineFunction(JSContext* cx, HandleObject obj, HandleId id, Native native, unsigned nargs, unsigned flags, AllocKind allocKind /* = AllocKind::FUNCTION */) diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 65da5e06f..d45d112a5 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -31,6 +31,12 @@ static const uint32_t JSSLOT_BOUND_FUNCTION_ARGS = 4; static const char FunctionConstructorMedialSigils[] = ") {\n"; static const char FunctionConstructorFinalBrace[] = "\n}"; +enum class FunctionPrefixKind { + None, + Get, + Set +}; + class JSFunction : public js::NativeObject { public: @@ -61,6 +67,9 @@ class JSFunction : public js::NativeObject function-statement) */ SELF_HOSTED = 0x0080, /* function is self-hosted builtin and must not be decompilable nor constructible. */ + HAS_COMPILE_TIME_NAME = 0x0100, /* function had no explicit name, but a + name was set by SetFunctionName + at compile time */ INTERPRETED_LAZY = 0x0200, /* function is interpreted but doesn't have a script yet */ RESOLVED_LENGTH = 0x0400, /* f.length has been resolved (see fun_resolve). */ RESOLVED_NAME = 0x0800, /* f.name has been resolved (see fun_resolve). */ @@ -94,7 +103,7 @@ class JSFunction : public js::NativeObject NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME, STABLE_ACROSS_CLONES = CONSTRUCTOR | EXPR_BODY | HAS_GUESSED_ATOM | LAMBDA | - SELF_HOSTED | FUNCTION_KIND_MASK + SELF_HOSTED | HAS_COMPILE_TIME_NAME | FUNCTION_KIND_MASK }; static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS, @@ -179,6 +188,7 @@ class JSFunction : public js::NativeObject /* Possible attributes of an interpreted function: */ bool isExprBody() const { return flags() & EXPR_BODY; } + bool hasCompileTimeName() const { return flags() & HAS_COMPILE_TIME_NAME; } bool hasGuessedAtom() const { return flags() & HAS_GUESSED_ATOM; } bool isLambda() const { return flags() & LAMBDA; } bool isBoundFunction() const { return flags() & BOUND_FUN; } @@ -220,7 +230,7 @@ class JSFunction : public js::NativeObject } bool isNamedLambda() const { - return isLambda() && displayAtom() && !hasGuessedAtom(); + return isLambda() && displayAtom() && !hasCompileTimeName() && !hasGuessedAtom(); } bool hasLexicalThis() const { @@ -308,14 +318,12 @@ class JSFunction : public js::NativeObject JSAtom* getUnresolvedName(JSContext* cx); - JSAtom* name() const { return hasGuessedAtom() ? nullptr : atom_.get(); } - - // Because display names (see Debugger.Object.displayName) are already stored - // on functions and will always contain a valid es6 function name, as described - // in "ECMA-262 (2016-02-27) 9.2.11 SetFunctionName," we have opted to save - // memory by parsing the existing display name when a function's name property - // is accessed. - JSAtom* functionName(JSContext* cx) const; + JSAtom* explicitName() const { + return (hasCompileTimeName() || hasGuessedAtom()) ? nullptr : atom_.get(); + } + JSAtom* explicitOrCompileTimeName() const { + return hasGuessedAtom() ? nullptr : atom_.get(); + } void initAtom(JSAtom* atom) { atom_.init(atom); } @@ -325,9 +333,24 @@ class JSFunction : public js::NativeObject return atom_; } + void setCompileTimeName(JSAtom* atom) { + MOZ_ASSERT(!atom_); + MOZ_ASSERT(atom); + MOZ_ASSERT(!hasGuessedAtom()); + MOZ_ASSERT(!isClassConstructor()); + atom_ = atom; + flags_ |= HAS_COMPILE_TIME_NAME; + } + JSAtom* compileTimeName() const { + MOZ_ASSERT(hasCompileTimeName()); + MOZ_ASSERT(atom_); + return atom_; + } + void setGuessedAtom(JSAtom* atom) { MOZ_ASSERT(!atom_); MOZ_ASSERT(atom); + MOZ_ASSERT(!hasCompileTimeName()); MOZ_ASSERT(!hasGuessedAtom()); atom_ = atom; flags_ |= HAS_GUESSED_ATOM; @@ -672,7 +695,16 @@ NewFunctionWithProto(ExclusiveContext* cx, JSNative native, unsigned nargs, NewFunctionProtoHandling protoHandling = NewFunctionClassProto); extern JSAtom* -IdToFunctionName(JSContext* cx, HandleId id, const char* prefix = nullptr); +IdToFunctionName(JSContext* cx, HandleId id, + FunctionPrefixKind prefixKind = FunctionPrefixKind::None); + +extern JSAtom* +NameToFunctionName(ExclusiveContext* cx, HandleAtom name, + FunctionPrefixKind prefixKind = FunctionPrefixKind::None); + +extern bool +SetFunctionNameIfNoOwnName(JSContext* cx, HandleFunction fun, HandleValue name, + FunctionPrefixKind prefixKind); extern JSFunction* DefineFunction(JSContext* cx, HandleObject obj, HandleId id, JSNative native, diff --git a/js/src/jsfuninlines.h b/js/src/jsfuninlines.h index 8286a7d6a..e134def61 100644 --- a/js/src/jsfuninlines.h +++ b/js/src/jsfuninlines.h @@ -16,7 +16,7 @@ namespace js { inline const char* GetFunctionNameBytes(JSContext* cx, JSFunction* fun, JSAutoByteString* bytes) { - if (JSAtom* name = fun->name()) + if (JSAtom* name = fun->explicitName()) return bytes->encodeLatin1(cx, name); return js_anonymous_str; } diff --git a/js/src/tests/ecma_6/Function/function-name-assignment.js b/js/src/tests/ecma_6/Function/function-name-assignment.js new file mode 100644 index 000000000..5e4d1c004 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-assignment.js @@ -0,0 +1,139 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on assignment"; + +print(BUGNUMBER + ": " + summary); + +var fooSymbol = Symbol("foo"); +var emptySymbol = Symbol(""); +var undefSymbol = Symbol(); +var globalVar; + +var exprs = [ + ["function() {}", false], + ["function named() {}", true], + ["function*() {}", false], + ["function* named() {}", true], + ["async function() {}", false], + ["async function named() {}", true], + ["() => {}", false], + ["async () => {}", false], + ["class {}", false], + ["class named {}", true], +]; + +function testAssignmentExpression(expr, named) { + eval(` + var assignment; + assignment = ${expr}; + assertEq(assignment.name, named ? "named" : "assignment"); + + globalVar = ${expr}; + assertEq(globalVar.name, named ? "named" : "globalVar"); + + var obj = { dynamic: null }; + with (obj) { + dynamic = ${expr}; + } + assertEq(obj.dynamic.name, named ? "named" : "dynamic"); + + (function namedLambda(param1, param2) { + var assignedToNamedLambda; + assignedToNamedLambda = namedLambda = ${expr}; + assertEq(namedLambda.name, "namedLambda"); + assertEq(assignedToNamedLambda.name, named ? "named" : "namedLambda"); + + param1 = ${expr}; + assertEq(param1.name, named ? "named" : "param1"); + + { + let param1 = ${expr}; + assertEq(param1.name, named ? "named" : "param1"); + + param2 = ${expr}; + assertEq(param2.name, named ? "named" : "param2"); + } + })(); + + { + let nextedLexical1, nextedLexical2; + { + let nextedLexical1 = ${expr}; + assertEq(nextedLexical1.name, named ? "named" : "nextedLexical1"); + + nextedLexical2 = ${expr}; + assertEq(nextedLexical2.name, named ? "named" : "nextedLexical2"); + } + } + `); + + // Not applicable cases: not IsIdentifierRef. + eval(` + var inParen; + (inParen) = ${expr}; + assertEq(inParen.name, named ? "named" : ""); + `); + + // Not applicable cases: not direct RHS. + if (!expr.includes("=>")) { + eval(` + var a = true && ${expr}; + assertEq(a.name, named ? "named" : ""); + `); + } else { + // Arrow function cannot be RHS of &&. + eval(` + var a = true && (${expr}); + assertEq(a.name, named ? "named" : ""); + `); + } + + // Not applicable cases: property. + eval(` + var obj = {}; + + obj.prop = ${expr}; + assertEq(obj.prop.name, named ? "named" : ""); + + obj["literal"] = ${expr}; + assertEq(obj["literal"].name, named ? "named" : ""); + `); + + // Not applicable cases: assigned again. + eval(` + var tmp = [${expr}]; + assertEq(tmp[0].name, named ? "named" : ""); + + var assignment; + assignment = tmp[0]; + assertEq(assignment.name, named ? "named" : ""); + `); +} +for (var [expr, named] of exprs) { + testAssignmentExpression(expr, named); +} + +function testVariableDeclaration(expr, named) { + eval(` + var varDecl = ${expr}; + assertEq(varDecl.name, named ? "named" : "varDecl"); + `); +} +for (var [expr, named] of exprs) { + testVariableDeclaration(expr, named); +} + +function testLexicalBinding(expr, named) { + eval(` + let lexical = ${expr}; + assertEq(lexical.name, named ? "named" : "lexical"); + + const constLexical = ${expr}; + assertEq(constLexical.name, named ? "named" : "constLexical"); + `); +} +for (var [expr, named] of exprs) { + testLexicalBinding(expr, named); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-binding.js b/js/src/tests/ecma_6/Function/function-name-binding.js new file mode 100644 index 000000000..bdd6c131c --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-binding.js @@ -0,0 +1,54 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on binding pattern"; + +print(BUGNUMBER + ": " + summary); + +var exprs = [ + ["function() {}", false], + ["function named() {}", true], + ["function*() {}", false], + ["function* named() {}", true], + ["async function() {}", false], + ["async function named() {}", true], + ["() => {}", false], + ["async () => {}", false], + ["class {}", false], + ["class named {}", true], +]; + +function testAssignmentProperty(expr, named) { + var f = eval(`(function({ prop1 = ${expr} }) { return prop1; })`); + assertEq(f({}).name, named ? "named" : "prop1"); + + eval(` + var { prop1 = ${expr} } = {}; + assertEq(prop1.name, named ? "named" : "prop1"); + `); +} +for (var [expr, named] of exprs) { + testAssignmentProperty(expr, named); +} + +function testAssignmentElement(expr, named) { + var f = eval(`(function([elem1 = ${expr}]) { return elem1; })`); + assertEq(f([]).name, named ? "named" : "elem1"); + + eval(` + var [elem1 = ${expr}] = []; + assertEq(elem1.name, named ? "named" : "elem1"); + `); +} +for (var [expr, named] of exprs) { + testAssignmentElement(expr, named); +} + +function testSingleNameBinding(expr, named) { + var f = eval(`(function(param1 = ${expr}) { return param1; })`); + assertEq(f().name, named ? "named" : "param1"); +} +for (var [expr, named] of exprs) { + testSingleNameBinding(expr, named); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-class.js b/js/src/tests/ecma_6/Function/function-name-class.js new file mode 100644 index 000000000..edde69055 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-class.js @@ -0,0 +1,32 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous class with name method shouldn't be affected by assignment"; + +print(BUGNUMBER + ": " + summary); + +var classWithStaticNameMethod = class { static name() {} }; +assertEq(typeof classWithStaticNameMethod.name, "function"); + +var classWithStaticNameGetter = class { static get name() { return "static name"; } }; +assertEq(typeof Object.getOwnPropertyDescriptor(classWithStaticNameGetter, "name").get, "function"); +assertEq(classWithStaticNameGetter.name, "static name"); + +var classWithStaticNameSetter = class { static set name(v) {} }; +assertEq(typeof Object.getOwnPropertyDescriptor(classWithStaticNameSetter, "name").set, "function"); + +var n = "NAME".toLowerCase(); +var classWithStaticNameMethodComputed = class { static [n]() {} }; +assertEq(typeof classWithStaticNameMethodComputed.name, "function"); + +// It doesn't apply for non-static method. + +var classWithNameMethod = class { name() {} }; +assertEq(classWithNameMethod.name, "classWithNameMethod"); + +var classWithNameGetter = class { get name() { return "name"; } }; +assertEq(classWithNameGetter.name, "classWithNameGetter"); + +var classWithNameSetter = class { set name(v) {} }; +assertEq(classWithNameSetter.name, "classWithNameSetter"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-for.js b/js/src/tests/ecma_6/Function/function-name-for.js new file mode 100644 index 000000000..2f04a5fa8 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-for.js @@ -0,0 +1,31 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on for-in initializer"; + +print(BUGNUMBER + ": " + summary); + +var exprs = [ + ["function() {}", false], + ["function named() {}", true], + ["function*() {}", false], + ["function* named() {}", true], + ["async function() {}", false], + ["async function named() {}", true], + ["() => {}", false], + ["async () => {}", false], + ["class {}", false], + ["class named {}", true], +]; + +function testForInHead(expr, named) { + eval(` + for (var forInHead = ${expr} in {}) { + } + `); + assertEq(forInHead.name, named ? "named" : "forInHead"); +} +for (var [expr, named] of exprs) { + testForInHead(expr, named); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-method.js b/js/src/tests/ecma_6/Function/function-name-method.js new file mode 100644 index 000000000..3b2eeee79 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-method.js @@ -0,0 +1,70 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on method definition"; + +print(BUGNUMBER + ": " + summary); + +var fooSymbol = Symbol("foo"); +var emptySymbol = Symbol(""); +var undefSymbol = Symbol(); + +function testMethod(prefix, classPrefix="", prototype=false) { + var param = (prefix == "set" || prefix == "static set") ? "v" : ""; + var sep = classPrefix ? "" : ","; + var objOrClass = eval(`(${classPrefix}{ + ${prefix} prop(${param}) {} ${sep} + ${prefix} "literal"(${param}) {} ${sep} + ${prefix} ""(${param}) {} ${sep} + ${prefix} 5(${param}) {} ${sep} + ${prefix} [Symbol.iterator](${param}) {} ${sep} + ${prefix} [fooSymbol](${param}) {} ${sep} + ${prefix} [emptySymbol](${param}) {} ${sep} + ${prefix} [undefSymbol](${param}) {} ${sep} + ${prefix} [/a/](${param}) {} ${sep} + })`); + + var target = prototype ? objOrClass.prototype : objOrClass; + + function testOne(methodName, expectedName) { + var f; + if (prefix == "get" || prefix == "static get") { + f = Object.getOwnPropertyDescriptor(target, methodName).get; + expectedName = "get " + expectedName; + } else if (prefix == "set" || prefix == "static set") { + f = Object.getOwnPropertyDescriptor(target, methodName).set; + expectedName = "set " + expectedName; + } else { + f = Object.getOwnPropertyDescriptor(target, methodName).value; + } + + assertEq(f.name, expectedName); + } + testOne("prop", "prop"); + testOne("literal", "literal"); + testOne("", ""); + testOne(5, "5"); + testOne(Symbol.iterator, "[Symbol.iterator]"); + testOne(fooSymbol, "[foo]"); + testOne(emptySymbol, "[]"); + testOne(undefSymbol, ""); + testOne(/a/, "/a/"); +} +testMethod(""); +testMethod("*"); +testMethod("async"); +testMethod("get"); +testMethod("set"); + +testMethod("", "class", true); +testMethod("*", "class", true); +testMethod("async", "class", true); +testMethod("get", "class", true); +testMethod("set", "class", true); + +testMethod("static", "class"); +testMethod("static *", "class"); +testMethod("static async", "class"); +testMethod("static get", "class"); +testMethod("static set", "class"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-property.js b/js/src/tests/ecma_6/Function/function-name-property.js new file mode 100644 index 000000000..7ad174b10 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-property.js @@ -0,0 +1,58 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on property name"; + +print(BUGNUMBER + ": " + summary); + +var fooSymbol = Symbol("foo"); +var emptySymbol = Symbol(""); +var undefSymbol = Symbol(); + +var exprs = [ + ["function() {}", false], + ["function named() {}", true], + ["function*() {}", false], + ["function* named() {}", true], + ["async function() {}", false], + ["async function named() {}", true], + ["() => {}", false], + ["async () => {}", false], + ["class {}", false], + ["class named {}", true], +]; + +function testPropertyDefinition(expr, named) { + var obj = eval(`({ + prop: ${expr}, + "literal": ${expr}, + "": ${expr}, + 5: ${expr}, + 0.4: ${expr}, + [Symbol.iterator]: ${expr}, + [fooSymbol]: ${expr}, + [emptySymbol]: ${expr}, + [undefSymbol]: ${expr}, + [/a/]: ${expr}, + })`); + assertEq(obj.prop.name, named ? "named" : "prop"); + assertEq(obj["literal"].name, named ? "named" : "literal"); + assertEq(obj[""].name, named ? "named" : ""); + assertEq(obj[5].name, named ? "named" : "5"); + assertEq(obj[0.4].name, named ? "named" : "0.4"); + assertEq(obj[Symbol.iterator].name, named ? "named" : "[Symbol.iterator]"); + assertEq(obj[fooSymbol].name, named ? "named" : "[foo]"); + assertEq(obj[emptySymbol].name, named ? "named" : "[]"); + assertEq(obj[undefSymbol].name, named ? "named" : ""); + assertEq(obj[/a/].name, named ? "named" : "/a/"); + + // Not applicable cases: __proto__. + obj = { + __proto__: function() {} + }; + assertEq(obj.__proto__.name, ""); +} +for (var [expr, named] of exprs) { + testPropertyDefinition(expr, named); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index 1e0c7d7c2..f50c87114 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -116,7 +116,7 @@ js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleOb // Create a new function with AsyncFunctionPrototype, reusing the name and // the length of `unwrapped`. - RootedAtom funName(cx, unwrapped->name()); + RootedAtom funName(cx, unwrapped->explicitName()); uint16_t length; if (!unwrapped->getLength(cx, &length)) return nullptr; @@ -130,6 +130,9 @@ js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleOb if (!wrapped) return nullptr; + if (unwrapped->hasCompileTimeName()) + wrapped->setCompileTimeName(unwrapped->compileTimeName()); + // Link them to each other to make GetWrappedAsyncFunction and // GetUnwrappedAsyncFunction work. unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped)); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 4d181545f..d16781326 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -9509,7 +9509,7 @@ DebuggerObject::name() const { MOZ_ASSERT(isFunction()); - return referent()->as().name(); + return referent()->as().explicitName(); } JSAtom* diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 039be2e32..280548cd6 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -802,10 +802,10 @@ GlobalObject::getSelfHostedFunction(JSContext* cx, Handle global, return false; if (exists) { RootedFunction fun(cx, &funVal.toObject().as()); - if (fun->name() == name) + if (fun->explicitName() == name) return true; - if (fun->name() == selfHostedName) { + if (fun->explicitName() == selfHostedName) { // This function was initially cloned because it was called by // other self-hosted code, so the clone kept its self-hosted name, // instead of getting the name it's intended to have in content diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 51e809345..9cba1f4dc 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1860,7 +1860,6 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) -CASE(JSOP_UNUSED182) CASE(JSOP_UNUSED183) CASE(JSOP_UNUSED187) CASE(JSOP_UNUSED192) @@ -3482,6 +3481,19 @@ CASE(JSOP_TOASYNC) } END_CASE(JSOP_TOASYNC) +CASE(JSOP_SETFUNNAME) +{ + MOZ_ASSERT(REGS.stackDepth() >= 2); + FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(REGS.pc)); + ReservedRooted name(&rootValue0, REGS.sp[-1]); + ReservedRooted fun(&rootFunction0, ®S.sp[-2].toObject().as()); + if (!SetFunctionNameIfNoOwnName(cx, fun, name, prefixKind)) + goto error; + + REGS.sp--; +} +END_CASE(JSOP_SETFUNNAME) + CASE(JSOP_CALLEE) MOZ_ASSERT(REGS.fp()->isFunctionFrame()); PUSH_COPY(REGS.fp()->calleev()); @@ -4343,7 +4355,7 @@ js::DefFunOperation(JSContext* cx, HandleScript script, HandleObject envChain, parent = parent->enclosingEnvironment(); /* ES5 10.5 (NB: with subsequent errata). */ - RootedPropertyName name(cx, fun->name()->asPropertyName()); + RootedPropertyName name(cx, fun->explicitName()->asPropertyName()); RootedShape shape(cx); RootedObject pobj(cx); @@ -4991,7 +5003,7 @@ js::ReportRuntimeLexicalError(JSContext* cx, unsigned errorNumber, RootedPropertyName name(cx); if (op == JSOP_THROWSETCALLEE) { - name = script->functionNonDelazifying()->name()->asPropertyName(); + name = script->functionNonDelazifying()->explicitName()->asPropertyName(); } else if (IsLocalOp(op)) { name = FrameSlotName(script, pc)->asPropertyName(); } else if (IsAtomOp(op)) { @@ -5059,8 +5071,8 @@ js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame) if (fun->isDerivedClassConstructor()) { const char* name = "anonymous"; JSAutoByteString str; - if (fun->name()) { - if (!AtomToPrintableString(cx, fun->name(), &str)) + if (fun->explicitName()) { + if (!AtomToPrintableString(cx, fun->explicitName(), &str)) return false; name = str.ptr(); } diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 18ae6f073..f6636004d 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1870,7 +1870,16 @@ * Stack: => */ \ macro(JSOP_POPVARENV, 181, "popvarenv", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED182, 182,"unused182", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Pops the top two values on the stack as 'name' and 'fun', defines the + * name of 'fun' to 'name' with prefix if any, and pushes 'fun' back onto + * the stack. + * Category: Statements + * Type: Function + * Operands: uint8_t prefixKind + * Stack: fun, name => fun + */ \ + macro(JSOP_SETFUNNAME, 182,"setfunname", NULL, 2, 2, 1, JOF_UINT8) \ macro(JSOP_UNUSED183, 183,"unused183", NULL, 1, 0, 0, JOF_BYTE) \ \ /* diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index fd604c6bf..bf49f2268 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2878,7 +2878,7 @@ CloneObject(JSContext* cx, HandleNativeObject selfHostedObject) RootedObject clone(cx); if (selfHostedObject->is()) { RootedFunction selfHostedFunction(cx, &selfHostedObject->as()); - bool hasName = selfHostedFunction->name() != nullptr; + bool hasName = selfHostedFunction->explicitName() != nullptr; // Arrow functions use the first extended slot for their lexical |this| value. MOZ_ASSERT(!selfHostedFunction->isArrow()); @@ -2894,7 +2894,7 @@ CloneObject(JSContext* cx, HandleNativeObject selfHostedObject) // self-hosting compartment has to be stored on the clone. if (clone && hasName) { clone->as().setExtendedSlot(LAZY_FUNCTION_NAME_SLOT, - StringValue(selfHostedFunction->name())); + StringValue(selfHostedFunction->explicitName())); } } else if (selfHostedObject->is()) { RegExpObject& reobj = selfHostedObject->as(); @@ -2977,10 +2977,10 @@ JSRuntime::createLazySelfHostedFunctionClone(JSContext* cx, HandlePropertyName s return false; if (!selfHostedFun->isClassConstructor() && !selfHostedFun->hasGuessedAtom() && - selfHostedFun->name() != selfHostedName) + selfHostedFun->explicitName() != selfHostedName) { MOZ_ASSERT(selfHostedFun->getExtendedSlot(HAS_SELFHOSTED_CANONICAL_NAME_SLOT).toBoolean()); - funName = selfHostedFun->name(); + funName = selfHostedFun->explicitName(); } fun.set(NewScriptedFunction(cx, nargs, JSFunction::INTERPRETED_LAZY, diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 2a7762e4f..3d09c7464 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -4541,7 +4541,7 @@ TypeScript::printTypes(JSContext* cx, HandleScript script) const uintptr_t(script.get()), script->filename(), script->lineno()); if (script->functionNonDelazifying()) { - if (JSAtom* name = script->functionNonDelazifying()->name()) + if (JSAtom* name = script->functionNonDelazifying()->explicitName()) name->dumpCharsNoNewline(); } diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 9d4ee94c6..ae97be0de 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -2870,7 +2870,7 @@ bool DataViewObject::defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto) { RootedId id(cx, NameToId(name)); - RootedAtom atom(cx, IdToFunctionName(cx, id, "get")); + RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Get)); if (!atom) return false; unsigned attrs = JSPROP_SHARED | JSPROP_GETTER; diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index d2f331bf1..b4f41c3d5 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -679,7 +679,7 @@ FunctionObject(ParseNode* fn) static inline PropertyName* FunctionName(ParseNode* fn) { - if (JSAtom* name = FunctionObject(fn)->name()) + if (JSAtom* name = FunctionObject(fn)->explicitName()) return name->asPropertyName(); return nullptr; } @@ -8037,7 +8037,7 @@ TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata static bool HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& metadata) { - RootedAtom name(cx, args.callee().as().name()); + RootedAtom name(cx, args.callee().as().explicitName()); if (cx->isExceptionPending()) return false; @@ -8128,7 +8128,7 @@ InstantiateAsmJS(JSContext* cx, unsigned argc, JS::Value* vp) static JSFunction* NewAsmJSModuleFunction(ExclusiveContext* cx, JSFunction* origFun, HandleObject moduleObj) { - RootedAtom name(cx, origFun->name()); + RootedAtom name(cx, origFun->explicitName()); JSFunction::Flags flags = origFun->isLambda() ? JSFunction::ASMJS_LAMBDA_CTOR : JSFunction::ASMJS_CTOR; @@ -8846,7 +8846,7 @@ js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda if (!out.append("function ")) return nullptr; - if (fun->name() && !out.append(fun->name())) + if (fun->explicitName() && !out.append(fun->explicitName())) return nullptr; bool haveSource = source->hasSourceData(); @@ -8894,8 +8894,8 @@ js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) if (!haveSource) { // asm.js functions can't be anonymous - MOZ_ASSERT(fun->name()); - if (!out.append(fun->name())) + MOZ_ASSERT(fun->explicitName()); + if (!out.append(fun->explicitName())) return nullptr; if (!out.append("() {\n [sourceless code]\n}")) return nullptr; -- cgit v1.2.3 From f67a2b88d7e8780f4ae81419338004b6fd781567 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 15:51:02 +0100 Subject: Part 2: Call NameFunctions after emitting Issue #78 --- js/src/frontend/BytecodeCompiler.cpp | 27 +++++++++++++++------------ js/src/frontend/BytecodeEmitter.cpp | 2 -- 2 files changed, 15 insertions(+), 14 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 3fbfdaa1a..76afe80b1 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -336,10 +336,10 @@ BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc) if (!deoptimizeArgumentsInEnclosingScripts(cx->asJSContext(), environment)) return nullptr; } - if (!NameFunctions(cx, pn)) - return nullptr; if (!emitter->emitScript(pn)) return nullptr; + if (!NameFunctions(cx, pn)) + return nullptr; parser->handler.freeTree(pn); break; @@ -397,15 +397,15 @@ BytecodeCompiler::compileModule() if (!pn) return nullptr; - if (!NameFunctions(cx, pn)) - return nullptr; - Maybe emitter; if (!emplaceEmitter(emitter, &modulesc)) return nullptr; if (!emitter->emitScript(pn->pn_body)) return nullptr; + if (!NameFunctions(cx, pn)) + return nullptr; + parser->handler.freeTree(pn); if (!builder.initModule()) @@ -453,9 +453,6 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, return false; } while (!fn); - if (!NameFunctions(cx, fn)) - return false; - if (fn->pn_funbox->function()->isInterpreted()) { MOZ_ASSERT(fun == fn->pn_funbox->function()); @@ -472,6 +469,9 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, MOZ_ASSERT(IsAsmJSModule(fun)); } + if (!NameFunctions(cx, fn)) + return false; + if (!maybeCompleteCompressSource()) return false; @@ -646,9 +646,6 @@ frontend::CompileLazyFunction(JSContext* cx, Handle lazy, const cha if (!pn) return false; - if (!NameFunctions(cx, pn)) - return false; - RootedScriptSource sourceObject(cx, lazy->sourceObject()); MOZ_ASSERT(sourceObject); @@ -667,7 +664,13 @@ frontend::CompileLazyFunction(JSContext* cx, Handle lazy, const cha if (!bce.init()) return false; - return bce.emitFunctionScript(pn->pn_body); + if (!bce.emitFunctionScript(pn->pn_body)) + return false; + + if (!NameFunctions(cx, pn)) + return false; + + return true; } bool diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 205bbf3d9..457218178 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4539,8 +4539,6 @@ BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); if (!funName) return false; - if (fun->hasGuessedAtom()) - fun->clearGuessedAtom(); fun->setCompileTimeName(name); return true; } -- cgit v1.2.3 From e3f1d0d10638e46e3a22cf8363c03922200cb158 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 15:59:22 +0100 Subject: Part 3: Support JSOP_SETFUNNAME in Baseline and Ion Issue #87 --- js/src/jit/BaselineCompiler.cpp | 25 +++++++++++++++++++++++++ js/src/jit/BaselineCompiler.h | 1 + js/src/jit/CodeGenerator.cpp | 14 ++++++++++++++ js/src/jit/CodeGenerator.h | 1 + js/src/jit/IonBuilder.cpp | 18 ++++++++++++++++++ js/src/jit/IonBuilder.h | 1 + js/src/jit/Lowering.cpp | 12 ++++++++++++ js/src/jit/Lowering.h | 1 + js/src/jit/MIR.h | 28 ++++++++++++++++++++++++++++ js/src/jit/MOpcodes.h | 1 + js/src/jit/shared/LIR-shared.h | 19 +++++++++++++++++++ js/src/jit/shared/LOpcodes-shared.h | 1 + 12 files changed, 122 insertions(+) (limited to 'js/src') diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index c58367aa3..4dcc10b61 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -9,6 +9,8 @@ #include "mozilla/Casting.h" #include "mozilla/SizePrintfMacros.h" +#include "jsfun.h" + #include "jit/BaselineIC.h" #include "jit/BaselineJIT.h" #include "jit/FixedList.h" @@ -1681,6 +1683,29 @@ BaselineCompiler::emit_JSOP_LAMBDA_ARROW() return true; } +typedef bool (*SetFunNameFn)(JSContext*, HandleFunction, HandleValue, FunctionPrefixKind); +static const VMFunction SetFunNameInfo = + FunctionInfo(js::SetFunctionNameIfNoOwnName, "SetFunName"); + +bool +BaselineCompiler::emit_JSOP_SETFUNNAME() +{ + frame.popRegsAndSync(2); + + frame.push(R0); + frame.syncStack(0); + + FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(pc)); + masm.unboxObject(R0, R0.scratchReg()); + + prepareVMCall(); + + pushArg(Imm32(int32_t(prefixKind))); + pushArg(R1); + pushArg(R0.scratchReg()); + return callVM(SetFunNameInfo); +} + void BaselineCompiler::storeValue(const StackValue* source, const Address& dest, const ValueOperand& scratch) diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 9adf65c27..77f4dd005 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -71,6 +71,7 @@ namespace jit { _(JSOP_REGEXP) \ _(JSOP_LAMBDA) \ _(JSOP_LAMBDA_ARROW) \ + _(JSOP_SETFUNNAME) \ _(JSOP_BITOR) \ _(JSOP_BITXOR) \ _(JSOP_BITAND) \ diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index ce97363be..3b5ec6baa 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2548,6 +2548,20 @@ CodeGenerator::emitLambdaInit(Register output, Register envChain, masm.storePtr(ImmGCPtr(info.fun->displayAtom()), Address(output, JSFunction::offsetOfAtom())); } +typedef bool (*SetFunNameFn)(JSContext*, HandleFunction, HandleValue, FunctionPrefixKind); +static const VMFunction SetFunNameInfo = + FunctionInfo(js::SetFunctionNameIfNoOwnName, "SetFunName"); + +void +CodeGenerator::visitSetFunName(LSetFunName* lir) +{ + pushArg(Imm32(lir->mir()->prefixKind())); + pushArg(ToValue(lir, LSetFunName::NameValue)); + pushArg(ToRegister(lir->fun())); + + callVM(SetFunNameInfo, lir); +} + void CodeGenerator::visitOsiPoint(LOsiPoint* lir) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 8f4bcc813..b69e919a3 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -134,6 +134,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitOutOfLineLambdaArrow(OutOfLineLambdaArrow* ool); void visitLambdaArrow(LLambdaArrow* lir); void visitLambdaForSingleton(LLambdaForSingleton* lir); + void visitSetFunName(LSetFunName* lir); void visitPointer(LPointer* lir); void visitKeepAliveObject(LKeepAliveObject* lir); void visitSlots(LSlots* lir); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 1488d7d34..2e7784ff4 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -2122,6 +2122,9 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_LAMBDA_ARROW: return jsop_lambda_arrow(info().getFunction(pc)); + case JSOP_SETFUNNAME: + return jsop_setfunname(GET_UINT8(pc)); + case JSOP_ITER: return jsop_iter(GET_INT8(pc)); @@ -13339,6 +13342,21 @@ IonBuilder::jsop_lambda_arrow(JSFunction* fun) return resumeAfter(ins); } +bool +IonBuilder::jsop_setfunname(uint8_t prefixKind) +{ + MDefinition* name = current->pop(); + MDefinition* fun = current->pop(); + MOZ_ASSERT(fun->type() == MIRType::Object); + + MSetFunName* ins = MSetFunName::New(alloc(), fun, name, prefixKind); + + current->add(ins); + current->push(fun); + + return resumeAfter(ins); +} + bool IonBuilder::jsop_setarg(uint32_t arg) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 38647a88f..0d1bdb1e3 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -766,6 +766,7 @@ class IonBuilder MOZ_MUST_USE bool jsop_object(JSObject* obj); MOZ_MUST_USE bool jsop_lambda(JSFunction* fun); MOZ_MUST_USE bool jsop_lambda_arrow(JSFunction* fun); + MOZ_MUST_USE bool jsop_setfunname(uint8_t prefixKind); MOZ_MUST_USE bool jsop_functionthis(); MOZ_MUST_USE bool jsop_globalthis(); MOZ_MUST_USE bool jsop_typeof(); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 13e50820e..a21a529be 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2459,6 +2459,18 @@ LIRGenerator::visitLambdaArrow(MLambdaArrow* ins) assignSafepoint(lir, ins); } +void +LIRGenerator::visitSetFunName(MSetFunName* ins) +{ + MOZ_ASSERT(ins->fun()->type() == MIRType::Object); + MOZ_ASSERT(ins->name()->type() == MIRType::Value); + + LSetFunName* lir = new(alloc()) LSetFunName(useRegisterAtStart(ins->fun()), + useBoxAtStart(ins->name())); + add(lir, ins); + assignSafepoint(lir, ins); +} + void LIRGenerator::visitKeepAliveObject(MKeepAliveObject* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 0f66a3c24..4062c0960 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -185,6 +185,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitNullarySharedStub(MNullarySharedStub* ins); void visitLambda(MLambda* ins); void visitLambdaArrow(MLambdaArrow* ins); + void visitSetFunName(MSetFunName* ins); void visitKeepAliveObject(MKeepAliveObject* ins); void visitSlots(MSlots* ins); void visitElements(MElements* ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index dcb08c317..3caa7e357 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -8464,6 +8464,34 @@ class MLambdaArrow } }; +class MSetFunName + : public MAryInstruction<2>, + public MixPolicy, BoxPolicy<1> >::Data +{ + uint8_t prefixKind_; + + explicit MSetFunName(MDefinition* fun, MDefinition* name, uint8_t prefixKind) + : prefixKind_(prefixKind) + { + initOperand(0, fun); + initOperand(1, name); + setResultType(MIRType::None); + } + + public: + INSTRUCTION_HEADER(SetFunName) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, fun), (1, name)) + + uint8_t prefixKind() const { + return prefixKind_; + } + + bool possiblyCalls() const override { + return true; + } +}; + // Returns obj->slots. class MSlots : public MUnaryInstruction, diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 74594cb35..1a6911247 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -161,6 +161,7 @@ namespace jit { _(StringReplace) \ _(Lambda) \ _(LambdaArrow) \ + _(SetFunName) \ _(KeepAliveObject) \ _(Slots) \ _(Elements) \ diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index a352f5d8a..f8e0ce9cc 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -4995,6 +4995,25 @@ class LLambdaArrow : public LInstructionHelper<1, 1 + BOX_PIECES, 0> } }; +class LSetFunName : public LCallInstructionHelper<1, 1 + BOX_PIECES, 0> +{ + public: + LIR_HEADER(SetFunName) + + static const size_t NameValue = 1; + + LSetFunName(const LAllocation& fun, const LBoxAllocation& name) { + setOperand(0, fun); + setBoxOperand(NameValue, name); + } + const LAllocation* fun() { + return getOperand(0); + } + const MSetFunName* mir() const { + return mir_->toSetFunName(); + } +}; + class LKeepAliveObject : public LInstructionHelper<0, 1, 0> { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index bb04553a6..e57751437 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -242,6 +242,7 @@ _(Lambda) \ _(LambdaArrow) \ _(LambdaForSingleton) \ + _(SetFunName) \ _(KeepAliveObject) \ _(Slots) \ _(Elements) \ -- cgit v1.2.3 From eb94521052069487322ab45aba9df6e29fc2ce8c Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 16:08:36 +0100 Subject: Part 4: Fix existing tests Issue #87 --- js/src/jit-test/tests/basic/constructor-name.js | 2 +- js/src/jit-test/tests/basic/functionnames.js | 32 +++++++++++----------- .../tests/debug/Memory-drainAllocationsLog-16.js | 2 +- .../tests/saved-stacks/function-display-name.js | 5 ++-- js/src/jit-test/tests/self-test/assertDeepEq.js | 3 +- .../ecma_5/extensions/error-tostring-function.js | 4 +-- js/src/tests/ecma_6/Class/className.js | 20 ++++++++------ js/src/tests/ecma_6/Object/accessor-name.js | 9 +++--- 8 files changed, 39 insertions(+), 38 deletions(-) (limited to 'js/src') diff --git a/js/src/jit-test/tests/basic/constructor-name.js b/js/src/jit-test/tests/basic/constructor-name.js index 4bc6a61ea..2dd4c073f 100644 --- a/js/src/jit-test/tests/basic/constructor-name.js +++ b/js/src/jit-test/tests/basic/constructor-name.js @@ -17,7 +17,7 @@ function makeObject() { let tests = [ { name: "Ctor", object: new Ctor }, { name: "nested.Ctor", object: new nested.Ctor }, - { name: "makeInstance/LexicalCtor", object: makeInstance() }, + { name: "LexicalCtor", object: makeInstance() }, { name: null, object: {} }, { name: null, object: nested.object }, { name: null, object: makeObject() }, diff --git a/js/src/jit-test/tests/basic/functionnames.js b/js/src/jit-test/tests/basic/functionnames.js index 935292ee3..7fef872fc 100644 --- a/js/src/jit-test/tests/basic/functionnames.js +++ b/js/src/jit-test/tests/basic/functionnames.js @@ -40,7 +40,7 @@ assertName(Foo, 'Foo new Ctor }, { name: "nested.Ctor", fn: () => new nested.Ctor }, - { name: "makeInstance/LexicalCtor", fn: () => makeInstance() }, + { name: "LexicalCtor", fn: () => makeInstance() }, { name: null, fn: () => ({}) }, { name: null, fn: () => (nested.object = {}) }, { name: null, fn: () => makeObject() }, diff --git a/js/src/jit-test/tests/saved-stacks/function-display-name.js b/js/src/jit-test/tests/saved-stacks/function-display-name.js index cfe175758..f10b7de6b 100644 --- a/js/src/jit-test/tests/saved-stacks/function-display-name.js +++ b/js/src/jit-test/tests/saved-stacks/function-display-name.js @@ -2,9 +2,8 @@ function uno() { return dos(); } const dos = () => tres.quattro(); -const tres = { - quattro: () => saveStack() -}; +let tres = {}; +tres.quattro = () => saveStack() const frame = uno(); diff --git a/js/src/jit-test/tests/self-test/assertDeepEq.js b/js/src/jit-test/tests/self-test/assertDeepEq.js index b2a949abc..9c1b37e8e 100644 --- a/js/src/jit-test/tests/self-test/assertDeepEq.js +++ b/js/src/jit-test/tests/self-test/assertDeepEq.js @@ -77,7 +77,8 @@ assertDeepEq(q, p); assertNotDeepEq(() => 1, () => 2); assertNotDeepEq((...x) => 1, x => 1); assertNotDeepEq(function f(){}, function g(){}); -var f1 = function () {}, f2 = function () {}; +// Avoid setting name property. +var [f1, f2] = [function () {}, function () {}]; assertDeepEq(f1, f1); assertDeepEq(f1, f2); // same text, close enough f1.prop = 1; diff --git a/js/src/tests/ecma_5/extensions/error-tostring-function.js b/js/src/tests/ecma_5/extensions/error-tostring-function.js index 5e92f1075..86751c39d 100644 --- a/js/src/tests/ecma_5/extensions/error-tostring-function.js +++ b/js/src/tests/ecma_5/extensions/error-tostring-function.js @@ -27,7 +27,7 @@ assertEq(ErrorToString(function(){}), ""); var fn1 = function() {}; fn1.message = "ohai"; -assertEq(ErrorToString(fn1), "ohai"); +assertEq(ErrorToString(fn1), "fn1: ohai"); var fn2 = function blerch() {}; fn2.message = "fnord"; @@ -35,7 +35,7 @@ assertEq(ErrorToString(fn2), "blerch: fnord"); var fn3 = function() {}; fn3.message = ""; -assertEq(ErrorToString(fn3), ""); +assertEq(ErrorToString(fn3), "fn3"); /******************************************************************************/ diff --git a/js/src/tests/ecma_6/Class/className.js b/js/src/tests/ecma_6/Class/className.js index a33397a8a..ad3920c15 100644 --- a/js/src/tests/ecma_6/Class/className.js +++ b/js/src/tests/ecma_6/Class/className.js @@ -174,27 +174,29 @@ testName(ExtendedExpr3, "base", false, false, false); // Anonymous class expressions don't get name properties unless specified in a // static manner. -let Anon = class { +// Use property assignment to avoid setting name property. +let tmp = {}; +let Anon = tmp.value = class { constructor() {} }; testName(Anon, "", false, false, false); -let AnonDefault = class { }; +let AnonDefault = tmp.value = class { }; testName(AnonDefault, "", false, false, false); -let AnonWithGetter = class { +let AnonWithGetter = tmp.value = class { constructor() {} static get name() { return "base"; } }; testName(AnonWithGetter, "base", false, true, false); -let AnonWithSetter = class { +let AnonWithSetter = tmp.value = class { constructor() {} static set name(v) {} }; testName(AnonWithSetter, undefined, false, false, true); -let AnonWithGetterSetter = class { +let AnonWithGetterSetter = tmp.value = class { constructor() {} static get name() { return "base"; } static set name(v) {} @@ -202,15 +204,15 @@ let AnonWithGetterSetter = class { testName(AnonWithGetterSetter, "base", false, true, true); -let ExtendedAnon1 = class extends Anon { +let ExtendedAnon1 = tmp.value = class extends Anon { constructor() {} }; testName(ExtendedAnon1, "", false, false, false); -let ExtendedAnonDefault = class extends Anon { }; +let ExtendedAnonDefault = tmp.value = class extends Anon { }; testName(ExtendedAnonDefault, "", false, false, false); -let ExtendedAnon2 = class extends AnonWithGetterSetter { +let ExtendedAnon2 = tmp.value = class extends AnonWithGetterSetter { constructor() {} static get name() { return "extend"; } }; @@ -218,7 +220,7 @@ testName(ExtendedAnon2, "extend", false, true, false); delete ExtendedAnon2.name; testName(ExtendedAnon2, "base", false, false, false); -let ExtendedAnon3 = class extends AnonWithGetterSetter { +let ExtendedAnon3 = tmp.value = class extends AnonWithGetterSetter { constructor() {} static set name(v) {} }; diff --git a/js/src/tests/ecma_6/Object/accessor-name.js b/js/src/tests/ecma_6/Object/accessor-name.js index 1b5268e07..f238a2aef 100644 --- a/js/src/tests/ecma_6/Object/accessor-name.js +++ b/js/src/tests/ecma_6/Object/accessor-name.js @@ -27,10 +27,9 @@ o = {get case() { }, set case(v) {}} assertEq(name(o, "case", true), "get case"); assertEq(name(o, "case", false), "set case"); -// Congratulations on implementing these! -assertEq(name({get ["a"]() {}}, "a", true), ""); -assertEq(name({get [123]() {}}, "123", true), ""); -assertEq(name({set ["a"](v) {}}, "a", false), ""); -assertEq(name({set [123](v) {}}, "123", false), ""); +assertEq(name({get ["a"]() {}}, "a", true), "get a"); +assertEq(name({get [123]() {}}, "123", true), "get 123"); +assertEq(name({set ["a"](v) {}}, "a", false), "set a"); +assertEq(name({set [123](v) {}}, "123", false), "set 123"); reportCompare(true, true); -- cgit v1.2.3 From 6085bfdcecc2529c1037f813e70583c2a776677d Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 18:11:08 +0100 Subject: Follow up: A opening bracket { was added; Added "function()->explicitName()" instead of "function()->name()" Issue #78 --- js/src/frontend/BytecodeEmitter.cpp | 2 +- js/src/frontend/Parser.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 457218178..acf734794 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -5158,7 +5158,7 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, if (!initializer && declList->isKind(PNK_VAR)) return true; - auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) + auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) { if (!initializer) { // Lexical declarations are initialized to undefined without an // initializer. diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 3106702cf..f4c02720a 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -838,7 +838,7 @@ Parser::reportBadReturn(Node pn, ParseReportKind kind, unsigned errnum, unsigned anonerrnum) { JSAutoByteString name; - if (JSAtom* atom = pc->functionBox()->function()->name()) { + if (JSAtom* atom = pc->functionBox()->function()->explicitName()) { if (!AtomToPrintableString(context, atom, &name)) return false; } else { -- cgit v1.2.3 From 3ee73ca14cec9ac99ebee938d76650ad11aa98da Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Tue, 20 Mar 2018 10:27:23 +0100 Subject: Bug 1322314 - Disallow emitting ParseNode twice Issue #73 [Depends on] Bug 1147371: Implement IteratorClose --- js/src/frontend/BytecodeEmitter.cpp | 29 ++++------------------------- js/src/frontend/BytecodeEmitter.h | 4 +--- js/src/frontend/ParseNode.h | 4 +--- 3 files changed, 6 insertions(+), 31 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index acf734794..ee26d0c43 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -6975,14 +6975,13 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) RootedFunction fun(cx, funbox->function()); RootedAtom name(cx, fun->explicitName()); MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); - MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto); /* * Set the |wasEmitted| flag in the funbox once the function has been * emitted. Function definitions that need hoisting to the top of the * function will be seen by emitFunction in two places. */ - if (funbox->wasEmitted && pn->functionIsHoisted()) { + if (funbox->wasEmitted) { // Annex B block-scoped functions are hoisted like any other // block-scoped function to the top of their scope. When their // definitions are seen for the second time, we need to emit the @@ -7111,7 +7110,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) } if (needsProto) { - MOZ_ASSERT(pn->getOp() == JSOP_FUNWITHPROTO || pn->getOp() == JSOP_LAMBDA); + MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA); pn->setOp(JSOP_FUNWITHPROTO); } @@ -10047,15 +10046,6 @@ CGConstList::finish(ConstArray* array) array->vector[i] = list[i]; } -bool -CGObjectList::isAdded(ObjectBox* objbox) -{ - // An objbox added to CGObjectList as non-first element has non-null - // emitLink member. The first element has null emitLink. - // Check for firstbox to cover the first element. - return objbox->emitLink || objbox == firstbox; -} - /* * Find the index of the given object for code generator. * @@ -10067,15 +10057,9 @@ CGObjectList::isAdded(ObjectBox* objbox) unsigned CGObjectList::add(ObjectBox* objbox) { - if (isAdded(objbox)) - return indexOf(objbox->object); - + MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; - - // See the comment in CGObjectList::isAdded. - if (!firstbox) - firstbox = objbox; return length++; } @@ -10102,12 +10086,7 @@ CGObjectList::finish(ObjectArray* array) MOZ_ASSERT(!*cursor); MOZ_ASSERT(objbox->object->isTenured()); *cursor = objbox->object; - - ObjectBox* tmp = objbox->emitLink; - // Clear emitLink for CGObjectList::isAdded. - objbox->emitLink = nullptr; - objbox = tmp; - } while (objbox != nullptr); + } while ((objbox = objbox->emitLink) != nullptr); MOZ_ASSERT(cursor == array->vector); } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 9a2ddb568..4b3750dd5 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -43,12 +43,10 @@ class CGConstList { struct CGObjectList { uint32_t length; /* number of emitted so far objects */ - ObjectBox* firstbox; /* first emitted object */ ObjectBox* lastbox; /* last emitted object */ - CGObjectList() : length(0), firstbox(nullptr), lastbox(nullptr) {} + CGObjectList() : length(0), lastbox(nullptr) {} - bool isAdded(ObjectBox* objbox); unsigned add(ObjectBox* objbox); unsigned indexOf(JSObject* obj); void finish(ObjectArray* array); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index ff26279af..c58dab431 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -649,14 +649,12 @@ class ParseNode MOZ_ASSERT(pn_arity == PN_CODE && getKind() == PNK_FUNCTION); MOZ_ASSERT(isOp(JSOP_LAMBDA) || // lambda, genexpr isOp(JSOP_LAMBDA_ARROW) || // arrow function - isOp(JSOP_FUNWITHPROTO) || // already emitted lambda with needsProto isOp(JSOP_DEFFUN) || // non-body-level function statement isOp(JSOP_NOP) || // body-level function stmt in global code isOp(JSOP_GETLOCAL) || // body-level function stmt in function code isOp(JSOP_GETARG) || // body-level function redeclaring formal isOp(JSOP_INITLEXICAL)); // block-level function stmt - return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && - !isOp(JSOP_FUNWITHPROTO) && !isOp(JSOP_DEFFUN); + return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN); } /* -- cgit v1.2.3 From caa2a53c402c7b509e9939e9aefe595dc0dbe516 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Tue, 20 Mar 2018 10:46:22 +0100 Subject: Bug 1322314 - Do not emit ParseNode twice in BytecodeEmitter::emitDestructuringOpsArray Issue #73 [Depends on] Bug 1147371: Implement IteratorClose --- js/src/frontend/BytecodeEmitter.cpp | 257 ++++++++++----------- js/src/frontend/BytecodeEmitter.h | 1 - .../ecma_6/Destructuring/array-default-class.js | 25 ++ 3 files changed, 144 insertions(+), 139 deletions(-) create mode 100644 js/src/tests/ecma_6/Destructuring/array-default-class.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index ee26d0c43..a68fe8538 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4462,13 +4462,6 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla return true; } -bool -BytecodeEmitter::emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav) -{ - TDZCheckCache tdzCache(this); - return emitDestructuringLHS(target, flav); -} - bool BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) { @@ -4739,7 +4732,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // // let x, y; // let a, b, c, d; - // let tmp, done, iter, result; // stack values + // let iter, result, done, value; // stack values // // iter = x[Symbol.iterator](); // @@ -4747,115 +4740,113 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // result = iter.next(); // done = result.done; // - // if (done) { - // a = undefined; - // - // result = undefined; - // done = true; - // } else { - // a = result.value; + // if (done) + // value = undefined; + // else + // value = result.value; // - // // Do next element's .next() and .done access here - // result = iter.next(); - // done = result.done; - // } + // a = value; // // // ==== emitted by loop for b ==== // if (done) { - // b = undefined; - // - // result = undefined; - // done = true; + // value = undefined; // } else { - // b = result.value; - // // result = iter.next(); // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // + // b = value; + // // // ==== emitted by loop for elision ==== // if (done) { - // result = undefined - // done = true + // value = undefined; // } else { - // result.value; - // // result = iter.next(); // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // // // ==== emitted by loop for c ==== // if (done) { - // c = y; + // value = undefined; // } else { - // tmp = result.value; - // if (tmp === undefined) - // tmp = y; - // c = tmp; - // - // // Don't do next element's .next() and .done access if - // // this is the last non-spread element. + // result = iter.next(); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // + // if (value === undefined) + // value = y; + // + // c = value; + // // // ==== emitted by loop for d ==== - // if (done) { - // // Assing empty array when completed - // d = []; - // } else { - // d = [...iter]; - // } + // if (done) + // value = []; + // else + // value = [...iter]; + // + // d = value; - /* - * Use an iterator to destructure the RHS, instead of index lookup. We - * must leave the *original* value on the stack. - */ + // Use an iterator to destructure the RHS, instead of index lookup. We + // must leave the *original* value on the stack. if (!emit1(JSOP_DUP)) // ... OBJ OBJ return false; - if (!emitIterator()) // ... OBJ? ITER + if (!emitIterator()) // ... OBJ ITER return false; - bool needToPopIterator = true; for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { bool isHead = member == pattern->pn_head; + bool hasNext = !!member->pn_next; + if (member->isKind(PNK_SPREAD)) { IfThenElseEmitter ifThenElse(this); if (!isHead) { // If spread is not the first element of the pattern, // iterator can already be completed. - if (!ifThenElse.emitIfElse()) // ... OBJ? ITER + // ... OBJ ITER DONE + if (!ifThenElse.emitIfElse()) // ... OBJ ITER return false; - if (!emit1(JSOP_POP)) // ... OBJ? - return false; - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ARRAY return false; - if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? - return false; - - if (!ifThenElse.emitElse()) // ... OBJ? ITER + if (!ifThenElse.emitElse()) // ... OBJ ITER return false; } // If iterator is not completed, create a new array with the rest // of the iterator. - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ITER ARRAY + if (!emit1(JSOP_DUP)) // ... OBJ ITER return false; - if (!emitNumberOp(0)) // ... OBJ? ITER ARRAY INDEX + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ITER ARRAY return false; - if (!emitSpread()) // ... OBJ? ARRAY INDEX + if (!emitNumberOp(0)) // ... OBJ ITER ITER ARRAY INDEX return false; - if (!emit1(JSOP_POP)) // ... OBJ? ARRAY + if (!emitSpread()) // ... OBJ ITER ARRAY INDEX return false; - if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? + if (!emit1(JSOP_POP)) // ... OBJ ITER ARRAY return false; if (!isHead) { if (!ifThenElse.emitEnd()) return false; - MOZ_ASSERT(ifThenElse.popped() == 1); + MOZ_ASSERT(ifThenElse.pushed() == 1); } - needToPopIterator = false; - MOZ_ASSERT(!member->pn_next); + + if (!emitDestructuringLHS(member, flav)) // ... OBJ ITER + return false; + + MOZ_ASSERT(!hasNext); break; } @@ -4867,110 +4858,100 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } bool isElision = subpattern->isKind(PNK_ELISION); - bool hasNextNonSpread = member->pn_next && !member->pn_next->isKind(PNK_SPREAD); - bool hasNextSpread = member->pn_next && member->pn_next->isKind(PNK_SPREAD); MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); - auto emitNext = [pattern](ExclusiveContext* cx, BytecodeEmitter* bce) { - if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER ITER - return false; - if (!bce->emitIteratorNext(pattern)) // ... OBJ? ITER RESULT + IfThenElseEmitter ifAlreadyDone(this); + if (!isHead) { + // If this element is not the first element of the pattern, + // iterator can already be completed. + // ... OBJ ITER DONE + if (hasNext) { + if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE DONE + return false; + } + if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE return false; - if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER RESULT RESULT + + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF return false; - if (!bce->emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ? ITER RESULT DONE? + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF return false; - return true; - }; - if (isHead) { - if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE return false; + + if (hasNext) { + if (!emit1(JSOP_POP)) // ... OBJ ITER + return false; + } } - IfThenElseEmitter ifThenElse(this); - if (!ifThenElse.emitIfElse()) // ... OBJ? ITER RESULT + if (!emit1(JSOP_DUP)) // ... OBJ ITER ITER return false; - - if (!emit1(JSOP_POP)) // ... OBJ? ITER + if (!emitIteratorNext(pattern)) // ... OBJ ITER RESULT return false; - if (pndefault) { - // Emit only pndefault tree here, as undefined check in emitDefault - // should always be true. - if (!emitInitializerInBranch(pndefault, subpattern)) // ... OBJ? ITER VALUE + if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER RESULT DONE + return false; + + if (hasNext) { + if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT DONE DONE return false; - } else { - if (!isElision) { - if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER UNDEFINED - return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) - return false; - } } - if (!isElision) { - if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER - return false; - } else if (pndefault) { - if (!emit1(JSOP_POP)) // ... OBJ? ITER + + IfThenElseEmitter ifDone(this); + if (!ifDone.emitIfElse()) // ... OBJ ITER RESULT ?DONE + return false; + + if (hasNext) { + if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT return false; } + if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE + return false; + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF + return false; + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF + return false; - // Setup next element's result when the iterator is done. - if (hasNextNonSpread) { - if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER RESULT - return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) - return false; - if (!emit1(JSOP_TRUE)) // ... OBJ? ITER RESULT DONE? - return false; - } else if (hasNextSpread) { - if (!emit1(JSOP_TRUE)) // ... OBJ? ITER DONE? + if (!ifDone.emitElse()) // ... OBJ ITER RESULT ?DONE + return false; + + if (hasNext) { + if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT return false; } - - if (!ifThenElse.emitElse()) // ... OBJ? ITER RESULT + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE VALUE return false; - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ? ITER VALUE + if (!ifDone.emitEnd()) return false; + MOZ_ASSERT(ifDone.pushed() == 0); - if (pndefault) { - if (!emitDefault(pndefault, subpattern)) // ... OBJ? ITER VALUE + if (!isHead) { + if (!ifAlreadyDone.emitEnd()) return false; + MOZ_ASSERT(ifAlreadyDone.pushed() == 1); } - if (!isElision) { - if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER - return false; - } else { - if (!emit1(JSOP_POP)) // ... OBJ? ITER + if (pndefault) { + if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE VALUE return false; } - // Setup next element's result when the iterator is not done. - if (hasNextNonSpread) { - if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + if (!isElision) { + if (!emitDestructuringLHS(subpattern, flav)) // ... OBJ ITER ?DONE return false; - } else if (hasNextSpread) { - if (!emit1(JSOP_FALSE)) // ... OBJ? ITER DONE? + } else { + if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE return false; } - - if (!ifThenElse.emitEnd()) - return false; - if (hasNextNonSpread) - MOZ_ASSERT(ifThenElse.pushed() == 1); - else if (hasNextSpread) - MOZ_ASSERT(ifThenElse.pushed() == 0); - else - MOZ_ASSERT(ifThenElse.popped() == 1); } - if (needToPopIterator) { - if (!emit1(JSOP_POP)) // ... OBJ? - return false; - } + if (!emit1(JSOP_POP)) // ... OBJ + return false; return true; } @@ -6118,7 +6099,7 @@ BytecodeEmitter::emitSpread(bool allowSelfHosted) return false; if (!emit1(JSOP_DUP)) // ITER ARR I RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE? + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER ARR I RESULT @@ -6307,7 +6288,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) @@ -6803,7 +6784,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; JumpList beq; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 4b3750dd5..f09b529ed 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -646,7 +646,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter // the stack and emits code to destructure a single lhs expression (either a // name or a compound []/{} expression). MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav); - MOZ_MUST_USE bool emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or diff --git a/js/src/tests/ecma_6/Destructuring/array-default-class.js b/js/src/tests/ecma_6/Destructuring/array-default-class.js new file mode 100644 index 000000000..5aa9c579b --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/array-default-class.js @@ -0,0 +1,25 @@ +var BUGNUMBER = 1322314; +var summary = "Function in computed property in class expression in array destructuring default"; + +print(BUGNUMBER + ": " + summary); + +function* g([ + a = class E { + [ (function() { return "foo"; })() ]() { + return 10; + } + } +]) { + yield a; +} + +let C = [...g([])][0]; +let x = new C(); +assertEq(x.foo(), 10); + +C = [...g([undefined])][0]; +x = new C(); +assertEq(x.foo(), 10); + +if (typeof reportCompare === "function") + reportCompare(0, 0); -- cgit v1.2.3 From 2a57d73c3b5304be3f9be51018a1bbee79f007e2 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Tue, 20 Mar 2018 12:40:00 +0100 Subject: Bug 1204028: Evaluate LHS reference before RHS in destructuring Issue #73 [Depends on] Bug 1147371: Implement IteratorClose --- js/src/frontend/BytecodeEmitter.cpp | 277 ++++++--- js/src/frontend/BytecodeEmitter.h | 18 +- js/src/jit/BaselineCompiler.cpp | 30 +- js/src/jit/BaselineCompiler.h | 1 + js/src/jit/IonBuilder.cpp | 5 + js/src/jit/MIRGraph.cpp | 13 + js/src/jit/MIRGraph.h | 3 + js/src/tests/ecma_6/Destructuring/order-super.js | 727 ++++++++++++++++++++++ js/src/tests/ecma_6/Destructuring/order.js | 745 +++++++++++++++++++++++ js/src/vm/Interpreter.cpp | 11 +- js/src/vm/Opcodes.h | 10 +- 11 files changed, 1743 insertions(+), 97 deletions(-) create mode 100644 js/src/tests/ecma_6/Destructuring/order-super.js create mode 100644 js/src/tests/ecma_6/Destructuring/order.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index a68fe8538..a4cfeb753 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4325,7 +4325,69 @@ BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitt } bool -BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav) +BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted) +{ + *emitted = 0; + + if (target->isKind(PNK_SPREAD)) + target = target->pn_kid; + else if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + + // No need to recur into PNK_ARRAY and PNK_OBJECT subpatterns here, since + // emitSetOrInitializeDestructuring does the recursion when setting or + // initializing value. Getting reference doesn't recur. + if (target->isKind(PNK_NAME) || target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) + return true; + +#ifdef DEBUG + int depth = stackDepth; +#endif + + switch (target->getKind()) { + case PNK_DOT: { + if (target->as().isSuper()) { + if (!emitSuperPropLHS(&target->as().expression())) + return false; + *emitted = 2; + } else { + if (!emitTree(target->pn_expr)) + return false; + *emitted = 1; + } + break; + } + + case PNK_ELEM: { + if (target->as().isSuper()) { + if (!emitSuperElemOperands(target, EmitElemOption::Ref)) + return false; + *emitted = 3; + } else { + if (!emitElemOperands(target, EmitElemOption::Ref)) + return false; + *emitted = 2; + } + break; + } + + case PNK_CALL: + MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); + } + + MOZ_ASSERT(stackDepth == depth + int(*emitted)); + + return true; +} + +bool +BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav) { // Now emit the lvalue opcode sequence. If the lvalue is a nested // destructuring initialiser-form, call ourselves to handle it, then pop @@ -4401,44 +4463,28 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla } case PNK_DOT: { - // See the (PNK_NAME, JSOP_SETNAME) case above. - // - // In `a.x = b`, `a` is evaluated first, then `b`, then a - // JSOP_SETPROP instruction. - // - // In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we - // need a property set -- but the operands are on the stack in the - // wrong order for JSOP_SETPROP, so we have to add a JSOP_SWAP. + // The reference is already pushed by emitDestructuringLHSRef. JSOp setOp; - if (target->as().isSuper()) { - if (!emitSuperPropLHS(&target->as().expression())) - return false; - if (!emit2(JSOP_PICK, 2)) - return false; + if (target->as().isSuper()) setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER; - } else { - if (!emitTree(target->pn_expr)) - return false; - if (!emit1(JSOP_SWAP)) - return false; + else setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; - } if (!emitAtomOp(target, setOp)) return false; break; } case PNK_ELEM: { - // See the comment at `case PNK_DOT:` above. This case, - // `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP - // is emitted by emitElemOperands. + // The reference is already pushed by emitDestructuringLHSRef. if (target->as().isSuper()) { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER; - if (!emitSuperElemOp(target, setOp)) + // emitDestructuringLHSRef already did emitSuperElemOperands + // part of emitSuperElemOp. Perform remaining part here. + if (!emitElemOpBase(setOp)) return false; } else { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; - if (!emitElemOp(target, setOp)) + if (!emitElemOpBase(setOp)) return false; } break; @@ -4451,7 +4497,7 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla break; default: - MOZ_CRASH("emitDestructuringLHS: bad lhs kind"); + MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind"); } // Pop the assigned value. @@ -4732,11 +4778,13 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // // let x, y; // let a, b, c, d; - // let iter, result, done, value; // stack values + // let iter, lref, result, done, value; // stack values // // iter = x[Symbol.iterator](); // // // ==== emitted by loop for a ==== + // lref = GetReference(a); + // // result = iter.next(); // done = result.done; // @@ -4745,9 +4793,11 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // else // value = result.value; // - // a = value; + // SetOrInitialize(lref, value); // // // ==== emitted by loop for b ==== + // lref = GetReference(b); + // // if (done) { // value = undefined; // } else { @@ -4759,7 +4809,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // value = result.value; // } // - // b = value; + // SetOrInitialize(lref, value); // // // ==== emitted by loop for elision ==== // if (done) { @@ -4774,6 +4824,8 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // } // // // ==== emitted by loop for c ==== + // lref = GetReference(c); + // // if (done) { // value = undefined; // } else { @@ -4788,15 +4840,17 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // if (value === undefined) // value = y; // - // c = value; + // SetOrInitialize(lref, value); // // // ==== emitted by loop for d ==== + // lref = GetReference(d); + // // if (done) // value = []; // else // value = [...iter]; // - // d = value; + // SetOrInitialize(lref, value); // Use an iterator to destructure the RHS, instead of index lookup. We // must leave the *original* value on the stack. @@ -4810,31 +4864,40 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav bool hasNext = !!member->pn_next; if (member->isKind(PNK_SPREAD)) { + size_t emitted = 0; + if (!emitDestructuringLHSRef(member, &emitted)) // ... OBJ ITER ?DONE *LREF + return false; + IfThenElseEmitter ifThenElse(this); if (!isHead) { // If spread is not the first element of the pattern, // iterator can already be completed. - // ... OBJ ITER DONE - if (!ifThenElse.emitIfElse()) // ... OBJ ITER + // ... OBJ ITER DONE *LREF + if (emitted) { + if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE + return false; + } + + if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF return false; - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ARRAY + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ARRAY return false; - if (!ifThenElse.emitElse()) // ... OBJ ITER + if (!ifThenElse.emitElse()) // ... OBJ ITER *LREF return false; } // If iterator is not completed, create a new array with the rest // of the iterator. - if (!emit1(JSOP_DUP)) // ... OBJ ITER + if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER return false; - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ITER ARRAY + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ITER ARRAY return false; - if (!emitNumberOp(0)) // ... OBJ ITER ITER ARRAY INDEX + if (!emitNumberOp(0)) // ... OBJ ITER *LREF ITER ARRAY INDEX return false; - if (!emitSpread()) // ... OBJ ITER ARRAY INDEX + if (!emitSpread()) // ... OBJ ITER *LREF ARRAY INDEX return false; - if (!emit1(JSOP_POP)) // ... OBJ ITER ARRAY + if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY return false; if (!isHead) { @@ -4843,7 +4906,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav MOZ_ASSERT(ifThenElse.pushed() == 1); } - if (!emitDestructuringLHS(member, flav)) // ... OBJ ITER + if (!emitSetOrInitializeDestructuring(member, flav)) // ... OBJ ITER return false; MOZ_ASSERT(!hasNext); @@ -4861,69 +4924,91 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); + size_t emitted = 0; + if (!isElision) { + if (!emitDestructuringLHSRef(subpattern, &emitted)) // ... OBJ ITER ?DONE *LREF + return false; + } + IfThenElseEmitter ifAlreadyDone(this); if (!isHead) { // If this element is not the first element of the pattern, // iterator can already be completed. - // ... OBJ ITER DONE - if (hasNext) { - if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE DONE - return false; + // ... OBJ ITER DONE *LREF + if (emitted) { + if (hasNext) { + if (!emitDupAt(emitted)) // ... OBJ ITER DONE *LREF DONE + return false; + } else { + if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE + return false; + } + } else { + if (hasNext) { + // The position of LREF in the following stack comment + // isn't accurate for the operation, but it's equivalent + // since LREF is nothing + if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE *LREF DONE + return false; + } } - if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE + if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF return false; - if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF return false; - if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE + if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE *LREF return false; if (hasNext) { - if (!emit1(JSOP_POP)) // ... OBJ ITER + if (emitted) { + if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE + return false; + } + if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF return false; } } - if (!emit1(JSOP_DUP)) // ... OBJ ITER ITER - return false; - if (!emitIteratorNext(pattern)) // ... OBJ ITER RESULT + if (emitted) { + if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER + return false; + } else { + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF ITER + return false; + } + if (!emitIteratorNext(pattern)) // ... OBJ ITER *LREF RESULT return false; - if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT RESULT + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER RESULT DONE + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE return false; if (hasNext) { - if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT DONE DONE + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE + return false; + if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE return false; } IfThenElseEmitter ifDone(this); - if (!ifDone.emitIfElse()) // ... OBJ ITER RESULT ?DONE + if (!ifDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF RESULT return false; - if (hasNext) { - if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT - return false; - } - if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE + if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE *LREF return false; - if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF return false; - if (!ifDone.emitElse()) // ... OBJ ITER RESULT ?DONE + if (!ifDone.emitElse()) // ... OBJ ITER ?DONE *LREF RESULT return false; - if (hasNext) { - if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT - return false; - } - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE VALUE + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE *LREF VALUE return false; if (!ifDone.emitEnd()) @@ -4937,13 +5022,16 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } if (pndefault) { - if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE VALUE + if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE *LREF VALUE return false; } if (!isElision) { - if (!emitDestructuringLHS(subpattern, flav)) // ... OBJ ITER ?DONE + if (!emitSetOrInitializeDestructuring(subpattern, + flav)) // ... OBJ ITER ?DONE + { return false; + } } else { if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE return false; @@ -4975,27 +5063,43 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla return false; for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { - // Duplicate the value being destructured to use as a reference base. - if (!emit1(JSOP_DUP)) // ... RHS RHS + ParseNode* subpattern; + if (member->isKind(PNK_MUTATEPROTO)) + subpattern = member->pn_kid; + else + subpattern = member->pn_right; + ParseNode* lhs = subpattern; + if (lhs->isKind(PNK_ASSIGN)) + lhs = lhs->pn_left; + + size_t emitted; + if (!emitDestructuringLHSRef(lhs, &emitted)) // ... RHS *LREF return false; + // Duplicate the value being destructured to use as a reference base. + if (emitted) { + if (!emitDupAt(emitted)) // ... RHS *LREF RHS + return false; + } else { + if (!emit1(JSOP_DUP)) // ... RHS RHS + return false; + } + // Now push the property name currently being matched, which is the // current property name "label" on the left of a colon in the object // initialiser. bool needsGetElem = true; - ParseNode* subpattern; if (member->isKind(PNK_MUTATEPROTO)) { - if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS PROP + if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; - subpattern = member->pn_kid; } else { MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); ParseNode* key = member->pn_left; if (key->isKind(PNK_NUMBER)) { - if (!emitNumberOp(key->pn_dval)) // ... RHS RHS KEY + if (!emitNumberOp(key->pn_dval)) // ... RHS *LREF RHS KEY return false; } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { PropertyName* name = key->pn_atom->asPropertyName(); @@ -5005,33 +5109,30 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla // as indexes for simplification of downstream analysis. jsid id = NameToId(name); if (id != IdToTypeId(id)) { - if (!emitTree(key)) // ... RHS RHS KEY + if (!emitTree(key)) // ... RHS *LREF RHS KEY return false; } else { - if (!emitAtomOp(name, JSOP_GETPROP)) // ...RHS PROP + if (!emitAtomOp(name, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; } } else { - if (!emitComputedPropertyName(key)) // ... RHS RHS KEY + if (!emitComputedPropertyName(key)) // ... RHS *LREF RHS KEY return false; } - - subpattern = member->pn_right; } // Get the property value if not done already. - if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS PROP + if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS *LREF PROP return false; if (subpattern->isKind(PNK_ASSIGN)) { - if (!emitDefault(subpattern->pn_right, subpattern->pn_left)) + if (!emitDefault(subpattern->pn_right, lhs)) // ... RHS *LREF VALUE return false; - subpattern = subpattern->pn_left; } - // Destructure PROP per this member's subpattern. - if (!emitDestructuringLHS(subpattern, flav)) + // Destructure PROP per this member's lhs. + if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... RHS return false; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index f09b529ed..066c06672 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -609,7 +609,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM // opcode onto the stack in the right order. In the case of SETELEM, the // value to be assigned must already be pushed. - enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign }; + enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign, Ref }; MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts); MOZ_MUST_USE bool emitElemOpBase(JSOp op); @@ -642,10 +642,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter DestructuringAssignment }; - // emitDestructuringLHS assumes the to-be-destructured value has been pushed on - // the stack and emits code to destructure a single lhs expression (either a - // name or a compound []/{} expression). - MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav); + // emitDestructuringLHSRef emits the lhs expression's reference. + // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. + // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. + // If there's nothing to evaluate for the reference, it emits nothing. + // |emitted| parameter receives the number of values pushed onto the stack. + MOZ_MUST_USE bool emitDestructuringLHSRef(ParseNode* target, size_t* emitted); + + // emitSetOrInitializeDestructuring assumes the lhs expression's reference + // and the to-be-destructured value has been pushed on the stack. It emits + // code to destructure a single lhs expression (either a name or a compound + // []/{} expression). + MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 4dcc10b61..2f0d41707 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1147,7 +1147,7 @@ BaselineCompiler::emit_JSOP_PICK() // after : A B D E C // First, move value at -(amount + 1) into R0. - int depth = -(GET_INT8(pc) + 1); + int32_t depth = -(GET_INT8(pc) + 1); masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); // Move the other values down. @@ -1165,6 +1165,34 @@ BaselineCompiler::emit_JSOP_PICK() return true; } +bool +BaselineCompiler::emit_JSOP_UNPICK() +{ + frame.syncStack(0); + + // Pick takes the top of the stack value and moves it under the nth value. + // For instance, unpick 2: + // before: A B C D E + // after : A B E C D + + // First, move value at -1 into R0. + masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); + + // Move the other values up. + int32_t depth = -(GET_INT8(pc) + 1); + for (int32_t i = -1; i > depth; i--) { + Address source = frame.addressOfStackValue(frame.peek(i - 1)); + Address dest = frame.addressOfStackValue(frame.peek(i)); + masm.loadValue(source, R1); + masm.storeValue(R1, dest); + } + + // Store R0 under the nth value. + Address dest = frame.addressOfStackValue(frame.peek(depth)); + masm.storeValue(R0, dest); + return true; +} + bool BaselineCompiler::emit_JSOP_GOTO() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 77f4dd005..60dac0966 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -42,6 +42,7 @@ namespace jit { _(JSOP_DUP2) \ _(JSOP_SWAP) \ _(JSOP_PICK) \ + _(JSOP_UNPICK) \ _(JSOP_GOTO) \ _(JSOP_IFEQ) \ _(JSOP_IFNE) \ diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2e7784ff4..c4df415a4 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1563,6 +1563,7 @@ IonBuilder::traverseBytecode() case JSOP_DUP: case JSOP_DUP2: case JSOP_PICK: + case JSOP_UNPICK: case JSOP_SWAP: case JSOP_SETARG: case JSOP_SETLOCAL: @@ -2017,6 +2018,10 @@ IonBuilder::inspectOpcode(JSOp op) current->pick(-GET_INT8(pc)); return true; + case JSOP_UNPICK: + current->unpick(-GET_INT8(pc)); + return true; + case JSOP_GETALIASEDVAR: return jsop_getaliasedvar(EnvironmentCoordinate(pc)); diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 3a363a5bf..d6e0fa8ff 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -789,6 +789,19 @@ MBasicBlock::pick(int32_t depth) swapAt(depth); } +void +MBasicBlock::unpick(int32_t depth) +{ + // unpick take the top of the stack element and move it under the depth-th + // element; + // unpick(-2): + // A B C D E + // A B C E D [ swapAt(-1) ] + // A B E C D [ swapAt(-2) ] + for (int32_t n = -1; n >= depth; n--) + swapAt(n); +} + void MBasicBlock::swapAt(int32_t depth) { diff --git a/js/src/jit/MIRGraph.h b/js/src/jit/MIRGraph.h index b986218f4..705d70fa1 100644 --- a/js/src/jit/MIRGraph.h +++ b/js/src/jit/MIRGraph.h @@ -142,6 +142,9 @@ class MBasicBlock : public TempObject, public InlineListNode // Move the definition to the top of the stack. void pick(int32_t depth); + // Move the top of the stack definition under the depth-th stack value. + void unpick(int32_t depth); + // Exchange 2 stack slots at the defined depth void swapAt(int32_t depth); diff --git a/js/src/tests/ecma_6/Destructuring/order-super.js b/js/src/tests/ecma_6/Destructuring/order-super.js new file mode 100644 index 000000000..afe11e2d9 --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/order-super.js @@ -0,0 +1,727 @@ +var BUGNUMBER = 1204028; +var summary = "Destructuring should evaluate lhs reference before rhs in super property"; + +if (typeof assertEq === "undefined") { + assertEq = function(a, b) { + if (a !== b) + throw new Error(`expected ${b}, got ${a}\n${new Error().stack}`); + }; +} + +print(BUGNUMBER + ": " + summary); + +let logs = []; +function log(x) { + logs.push(x); +} + +let unwrapMap = new Map(); +function unwrap(maybeWrapped) { + if (unwrapMap.has(maybeWrapped)) + return unwrapMap.get(maybeWrapped); + return maybeWrapped; +} +function ToString(name) { + if (name == Symbol.iterator) + return "@@iterator"; + return String(name); +} +function logger(obj, prefix=[]) { + let wrapped = new Proxy(obj, { + get(that, name) { + if (name == "return") { + // FIXME: Bug 1147371. + // We ignore IteratorClose for now. + return obj[name]; + } + + let names = prefix.concat(ToString(name)); + log("rhs get " + names.join("::")); + let v = obj[name]; + if (typeof v === "object" || typeof v === "function") + return logger(v, names); + return v; + }, + apply(that, thisArg, args) { + let names = prefix.slice(); + log("rhs call " + names.join("::")); + let v = obj.apply(unwrap(thisArg), args); + if (typeof v === "object" || typeof v === "function") { + names[names.length - 1] += "()"; + return logger(v, names); + } + return v; + } + }); + unwrapMap.set(wrapped, obj); + return wrapped; +} + +class C1 { + constructor() { + this.clear(); + } + clear() { + this.values = {}; + } +} +for (let name of [ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "0", "1", "length" +]) { + Object.defineProperty(C1.prototype, name, { + set: function(value) { + log("lhs set " + name); + this.values[name] = value; + } + }); +} +class C2 extends C1 { + constructor() { + super(); + + let clear = () => { + logs = []; + this.clear(); + }; + + // Array. + + clear(); + [ + super.a + ] = logger(["A"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + clear(); + [ + super[ (log("lhs before name a"), "a") ] + ] = logger(["A"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Array rest. + + clear(); + [ + ...super.a + ] = logger(["A", "B", "C"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); + assertEq(this.values.a.join(","), "A,B,C"); + + clear(); + [ + ...super[ (log("lhs before name a"), "a") ] + ] = logger(["A", "B", "C"]);; + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); + assertEq(this.values.a.join(","), "A,B,C"); + + // Array combined. + + clear(); + [ + super.a, + super[ (log("lhs before name b"), "b") ], + ...super[ (log("lhs before name c"), "c") ] + ] = logger(["A", "B", "C"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "lhs before name b", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set b", + + "lhs before name c", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set c", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + assertEq(this.values.c.join(","), "C"); + + // Object. + + clear(); + ({ + a: super.a + } = logger({a: "A"})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + clear(); + ({ + a: super[ (log("lhs before name a"), "a") ] + } = logger({a: "A"})); + assertEq(logs.join(","), + [ + "lhs before name a", + "rhs get a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Object combined. + + clear(); + ({ + a: super.a, + b: super[ (log("lhs before name b"), "b") ] + } = logger({a: "A", b: "B"})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs set a", + + "lhs before name b", + "rhs get b", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + + // == Nested == + + // Array -> Array + + clear(); + [ + [ + super[ (log("lhs before name a"), "a") ], + ...super[ (log("lhs before name b"), "b") ] + ] + ] = logger([["A", "B"]]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name a", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set a", + + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b.length, 1); + assertEq(this.values.b[0], "B"); + + // Array rest -> Array + + clear(); + [ + ...[ + super[ (log("lhs before name a"), "a") ], + ...super[ (log("lhs before name b"), "b") ] + ] + ] = logger(["A", "B"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name a", + "lhs set a", + + "lhs before name b", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b.join(","), "B"); + + // Array -> Object + clear(); + [ + { + a: super[ (log("lhs before name a"), "a") ] + } + ] = logger([{a: "A"}]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before name a", + "rhs get @@iterator()::next()::value::a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Array rest -> Object + clear(); + [ + ...{ + 0: super[ (log("lhs before name 0"), "0") ], + 1: super[ (log("lhs before name 1"), "1") ], + length: super[ (log("lhs before name length"), "length") ], + } + ] = logger(["A", "B"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name 0", + "lhs set 0", + + "lhs before name 1", + "lhs set 1", + + "lhs before name length", + "lhs set length", + ].join(",")); + assertEq(this.values["0"], "A"); + assertEq(this.values["1"], "B"); + assertEq(this.values.length, 2); + + // Object -> Array + clear(); + ({ + a: [ + super[ (log("lhs before name b"), "b") ] + ] + } = logger({a: ["B"]})); + assertEq(logs.join(","), + [ + "rhs get a", + "rhs get a::@@iterator", + "rhs call a::@@iterator", + + "lhs before name b", + "rhs get a::@@iterator()::next", + "rhs call a::@@iterator()::next", + "rhs get a::@@iterator()::next()::done", + "rhs get a::@@iterator()::next()::value", + "lhs set b", + ].join(",")); + assertEq(this.values.b, "B"); + + // Object -> Object + clear(); + ({ + a: { + b: super[ (log("lhs before name b"), "b") ] + } + } = logger({a: {b: "B"}})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs before name b", + "rhs get a::b", + "lhs set b", + ].join(",")); + assertEq(this.values.b, "B"); + + // All combined + + clear(); + [ + super[ (log("lhs before name a"), "a") ], + [ + super[ (log("lhs before name b"), "b") ], + { + c: super[ (log("lhs before name c"), "c") ], + d: { + e: super[ (log("lhs before name e"), "e") ], + f: [ + super[ (log("lhs before name g"), "g") ] + ] + } + } + ], + { + h: super[ (log("lhs before name h"), "h") ], + i: [ + super[ (log("lhs before name j"), "j") ], + { + k: [ + super[ (log("lhs before name l"), "l") ] + ] + } + ] + }, + ...[ + super[ (log("lhs before name m"), "m") ], + [ + super[ (log("lhs before name n"), "n") ], + { + o: super[ (log("lhs before name o"), "o") ], + p: { + q: super[ (log("lhs before name q"), "q") ], + r: [ + super[ (log("lhs before name s"), "s") ] + ] + } + } + ], + ...{ + 0: super[ (log("lhs before name t"), "t") ], + 1: [ + super[ (log("lhs before name u"), "u") ], + { + v: super[ (log("lhs before name v"), "v") ], + w: { + x: super[ (log("lhs before name x"), "x") ], + y: [ + super[ (log("lhs before name z"), "z") ] + ] + } + } + ], + length: super[ (log("lhs before name length"), "length") ], + } + ] + ] = logger(["A", + ["B", {c: "C", d: {e: "E", f: ["G"]}}], + {h: "H", i: ["J", {k: ["L"]}]}, + "M", + ["N", {o: "O", p: {q: "Q", r: ["S"]}}], + "T", ["U", {v: "V", w: {x: "X", y: ["Z"]}}]]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set b", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name c", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::c", + "lhs set c", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d", + + "lhs before name e", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::e", + "lhs set e", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + + "lhs before name g", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::value", + "lhs set g", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before name h", + "rhs get @@iterator()::next()::value::h", + "lhs set h", + + "rhs get @@iterator()::next()::value::i", + "rhs get @@iterator()::next()::value::i::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator", + + "lhs before name j", + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + "lhs set j", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + + "lhs before name l", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::value", + "lhs set l", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name m", + "lhs set m", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name n", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set n", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name o", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::o", + "lhs set o", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p", + + "lhs before name q", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::q", + "lhs set q", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + + "lhs before name s", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::value", + "lhs set s", + + "lhs before name t", + "lhs set t", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name u", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set u", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name v", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::v", + "lhs set v", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w", + + "lhs before name x", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::x", + "lhs set x", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + + "lhs before name z", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::value", + "lhs set z", + + "lhs before name length", + "lhs set length", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + assertEq(this.values.c, "C"); + assertEq(this.values.e, "E"); + assertEq(this.values.g, "G"); + assertEq(this.values.h, "H"); + assertEq(this.values.j, "J"); + assertEq(this.values.l, "L"); + assertEq(this.values.m, "M"); + assertEq(this.values.n, "N"); + assertEq(this.values.o, "O"); + assertEq(this.values.q, "Q"); + assertEq(this.values.s, "S"); + assertEq(this.values.t, "T"); + assertEq(this.values.u, "U"); + assertEq(this.values.v, "V"); + assertEq(this.values.x, "X"); + assertEq(this.values.z, "Z"); + assertEq(this.values.length, 2); + } +} + +new C2(); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Destructuring/order.js b/js/src/tests/ecma_6/Destructuring/order.js new file mode 100644 index 000000000..4eb48cd2b --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/order.js @@ -0,0 +1,745 @@ +var BUGNUMBER = 1204028; +var summary = "Destructuring should evaluate lhs reference before rhs"; + +print(BUGNUMBER + ": " + summary); + +let storage = { + clear() { + this.values = {}; + } +}; +storage.clear(); +let obj = new Proxy(storage, { + set(that, name, value) { + log("lhs set " + name); + storage.values[name] = value; + } +}); + +let logs = []; +function log(x) { + logs.push(x); +} + +function clear() { + logs = []; + storage.clear(); +} + +let unwrapMap = new Map(); +function unwrap(maybeWrapped) { + if (unwrapMap.has(maybeWrapped)) + return unwrapMap.get(maybeWrapped); + return maybeWrapped; +} +function ToString(name) { + if (name == Symbol.iterator) + return "@@iterator"; + return String(name); +} +function logger(obj, prefix=[]) { + let wrapped = new Proxy(obj, { + get(that, name) { + if (name == "return") { + // FIXME: Bug 1147371. + // We ignore IteratorClose for now. + return obj[name]; + } + + let names = prefix.concat(ToString(name)); + log("rhs get " + names.join("::")); + let v = obj[name]; + if (typeof v === "object" || typeof v === "function") + return logger(v, names); + return v; + }, + apply(that, thisArg, args) { + let names = prefix.slice(); + log("rhs call " + names.join("::")); + let v = obj.apply(unwrap(thisArg), args); + if (typeof v === "object" || typeof v === "function") { + names[names.length - 1] += "()"; + return logger(v, names); + } + return v; + } + }); + unwrapMap.set(wrapped, obj); + return wrapped; +} + +// Array. + +clear(); +[ + ( log("lhs before obj a"), obj ).a +] = logger(["A"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +clear(); +[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +] = logger(["A"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Array rest. + +clear(); +[ + ...( log("lhs before obj a"), obj ).a +] = logger(["A", "B", "C"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); +assertEq(storage.values.a.join(","), "A,B,C"); + +clear(); +[ + ...( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +] = logger(["A", "B", "C"]);; +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); +assertEq(storage.values.a.join(","), "A,B,C"); + +// Array combined. + +clear(); +[ + ( log("lhs before obj a"), obj ).a, + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ], + ...( log("lhs before obj c"), obj )[ (log("lhs before name c"), "c") ] +] = logger(["A", "B", "C"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set b", + + "lhs before obj c", + "lhs before name c", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set c", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); +assertEq(storage.values.c.join(","), "C"); + +// Object. + +clear(); +({ + a: ( log("lhs before obj a"), obj ).a +} = logger({a: "A"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "rhs get a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +clear(); +({ + a: ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +} = logger({a: "A"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "lhs before name a", + "rhs get a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Object combined. + +clear(); +({ + a: ( log("lhs before obj a"), obj ).a, + b: ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] +} = logger({a: "A", b: "B"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "rhs get a", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get b", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); + +// == Nested == + +// Array -> Array + +clear(); +[ + [ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + ...( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +] = logger([["A", "B"]]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b.length, 1); +assertEq(storage.values.b[0], "B"); + +// Array rest -> Array + +clear(); +[ + ...[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + ...( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +] = logger(["A", "B"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj a", + "lhs before name a", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b.join(","), "B"); + +// Array -> Object +clear(); +[ + { + a: ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] + } +] = logger([{a: "A"}]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next()::value::a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Array rest -> Object +clear(); +[ + ...{ + 0: ( log("lhs before obj 0"), obj )[ (log("lhs before name 0"), "0") ], + 1: ( log("lhs before obj 1"), obj )[ (log("lhs before name 1"), "1") ], + length: ( log("lhs before obj length"), obj )[ (log("lhs before name length"), "length") ], + } +] = logger(["A", "B"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj 0", + "lhs before name 0", + "lhs set 0", + + "lhs before obj 1", + "lhs before name 1", + "lhs set 1", + + "lhs before obj length", + "lhs before name length", + "lhs set length", + ].join(",")); +assertEq(storage.values["0"], "A"); +assertEq(storage.values["1"], "B"); +assertEq(storage.values.length, 2); + +// Object -> Array +clear(); +({ + a: [ + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +} = logger({a: ["B"]})); +assertEq(logs.join(","), + [ + "rhs get a", + "rhs get a::@@iterator", + "rhs call a::@@iterator", + + "lhs before obj b", + "lhs before name b", + "rhs get a::@@iterator()::next", + "rhs call a::@@iterator()::next", + "rhs get a::@@iterator()::next()::done", + "rhs get a::@@iterator()::next()::value", + "lhs set b", + ].join(",")); +assertEq(storage.values.b, "B"); + +// Object -> Object +clear(); +({ + a: { + b: ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + } +} = logger({a: {b: "B"}})); +assertEq(logs.join(","), + [ + "rhs get a", + "lhs before obj b", + "lhs before name b", + "rhs get a::b", + "lhs set b", + ].join(",")); +assertEq(storage.values.b, "B"); + +// All combined + +clear(); +[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + [ + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ], + { + c: ( log("lhs before obj c"), obj )[ (log("lhs before name c"), "c") ], + d: { + e: ( log("lhs before obj e"), obj )[ (log("lhs before name e"), "e") ], + f: [ + ( log("lhs before obj g"), obj )[ (log("lhs before name g"), "g") ] + ] + } + } + ], + { + h: ( log("lhs before obj h"), obj )[ (log("lhs before name h"), "h") ], + i: [ + ( log("lhs before obj j"), obj )[ (log("lhs before name j"), "j") ], + { + k: [ + ( log("lhs before obj l"), obj )[ (log("lhs before name l"), "l") ] + ] + } + ] + }, + ...[ + ( log("lhs before obj m"), obj )[ (log("lhs before name m"), "m") ], + [ + ( log("lhs before obj n"), obj )[ (log("lhs before name n"), "n") ], + { + o: ( log("lhs before obj o"), obj )[ (log("lhs before name o"), "o") ], + p: { + q: ( log("lhs before obj q"), obj )[ (log("lhs before name q"), "q") ], + r: [ + ( log("lhs before obj s"), obj )[ (log("lhs before name s"), "s") ] + ] + } + } + ], + ...{ + 0: ( log("lhs before obj t"), obj )[ (log("lhs before name t"), "t") ], + 1: [ + ( log("lhs before obj u"), obj )[ (log("lhs before name u"), "u") ], + { + v: ( log("lhs before obj v"), obj )[ (log("lhs before name v"), "v") ], + w: { + x: ( log("lhs before obj x"), obj )[ (log("lhs before name x"), "x") ], + y: [ + ( log("lhs before obj z"), obj )[ (log("lhs before name z"), "z") ] + ] + } + } + ], + length: ( log("lhs before obj length"), obj )[ (log("lhs before name length"), "length") ], + } + ] +] = logger(["A", + ["B", {c: "C", d: {e: "E", f: ["G"]}}], + {h: "H", i: ["J", {k: ["L"]}]}, + "M", + ["N", {o: "O", p: {q: "Q", r: ["S"]}}], + "T", ["U", {v: "V", w: {x: "X", y: ["Z"]}}]]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set b", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj c", + "lhs before name c", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::c", + "lhs set c", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d", + + "lhs before obj e", + "lhs before name e", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::e", + "lhs set e", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + + "lhs before obj g", + "lhs before name g", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::value", + "lhs set g", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before obj h", + "lhs before name h", + "rhs get @@iterator()::next()::value::h", + "lhs set h", + + "rhs get @@iterator()::next()::value::i", + "rhs get @@iterator()::next()::value::i::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator", + + "lhs before obj j", + "lhs before name j", + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + "lhs set j", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + + "lhs before obj l", + "lhs before name l", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::value", + "lhs set l", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj m", + "lhs before name m", + "lhs set m", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj n", + "lhs before name n", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set n", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj o", + "lhs before name o", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::o", + "lhs set o", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p", + + "lhs before obj q", + "lhs before name q", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::q", + "lhs set q", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + + "lhs before obj s", + "lhs before name s", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::value", + "lhs set s", + + "lhs before obj t", + "lhs before name t", + "lhs set t", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj u", + "lhs before name u", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set u", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj v", + "lhs before name v", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::v", + "lhs set v", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w", + + "lhs before obj x", + "lhs before name x", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::x", + "lhs set x", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + + "lhs before obj z", + "lhs before name z", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::value", + "lhs set z", + + "lhs before obj length", + "lhs before name length", + "lhs set length", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); +assertEq(storage.values.c, "C"); +assertEq(storage.values.e, "E"); +assertEq(storage.values.g, "G"); +assertEq(storage.values.h, "H"); +assertEq(storage.values.j, "J"); +assertEq(storage.values.l, "L"); +assertEq(storage.values.m, "M"); +assertEq(storage.values.n, "N"); +assertEq(storage.values.o, "O"); +assertEq(storage.values.q, "Q"); +assertEq(storage.values.s, "S"); +assertEq(storage.values.t, "T"); +assertEq(storage.values.u, "U"); +assertEq(storage.values.v, "V"); +assertEq(storage.values.x, "X"); +assertEq(storage.values.z, "Z"); +assertEq(storage.values.length, 2); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 9cba1f4dc..8ae9c43b0 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1860,7 +1860,6 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) -CASE(JSOP_UNUSED183) CASE(JSOP_UNUSED187) CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED209) @@ -2193,6 +2192,16 @@ CASE(JSOP_PICK) } END_CASE(JSOP_PICK) +CASE(JSOP_UNPICK) +{ + int i = GET_UINT8(REGS.pc); + MOZ_ASSERT(REGS.stackDepth() >= unsigned(i) + 1); + Value lval = REGS.sp[-1]; + memmove(REGS.sp - i, REGS.sp - (i + 1), sizeof(Value) * i); + REGS.sp[-(i + 1)] = lval; +} +END_CASE(JSOP_UNPICK) + CASE(JSOP_BINDGNAME) CASE(JSOP_BINDNAME) { diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index f6636004d..b59b9388c 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1880,8 +1880,14 @@ * Stack: fun, name => fun */ \ macro(JSOP_SETFUNNAME, 182,"setfunname", NULL, 2, 2, 1, JOF_UINT8) \ - macro(JSOP_UNUSED183, 183,"unused183", NULL, 1, 0, 0, JOF_BYTE) \ - \ + /* + * Moves the top of the stack value under the nth element of the stack. + * Category: Operators + * Type: Stack Operations + * Operands: uint8_t n + * Stack: v[n], v[n-1], ..., v[1], v[0] => v[0], v[n], v[n-1], ..., v[1] + */ \ + macro(JSOP_UNPICK, 183,"unpick", NULL, 2, 0, 0, JOF_UINT8) \ /* * Pops the top of stack value, pushes property of it onto the stack. * -- cgit v1.2.3 From 7d753c1a8f22f85f6279a3c016034ce8f8e740f7 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 12:09:30 +0100 Subject: Bug 1147371: Implement IteratorClose for for-of Issue #74 --- js/src/builtin/TypedArray.js | 4 +- js/src/frontend/BytecodeEmitter.cpp | 619 +++++++++++++++------ js/src/frontend/BytecodeEmitter.h | 6 + js/src/jit/JitFrames.cpp | 39 +- js/src/js.msg | 2 +- js/src/jsiter.cpp | 52 ++ js/src/jsiter.h | 3 + js/src/jsscript.h | 3 +- js/src/shell/js.cpp | 9 +- .../ecma_6/Statements/for-of-iterator-close.js | 102 ++++ js/src/tests/ecma_6/shell.js | 48 +- js/src/vm/Interpreter.cpp | 19 +- js/src/vm/Interpreter.h | 1 + 13 files changed, 704 insertions(+), 203 deletions(-) create mode 100644 js/src/tests/ecma_6/Statements/for-of-iterator-close.js (limited to 'js/src') diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 4a3f38365..a2205dc92 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -1428,7 +1428,7 @@ function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) { // 22.2.2.1.1 IterableToList, step 4.a. var next = callContentFunction(iterator.next, iterator); if (!IsObject(next)) - ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); + ThrowTypeError(JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next"); // 22.2.2.1.1 IterableToList, step 4.b. if (next.done) @@ -1555,7 +1555,7 @@ function IterableToList(items, method) { // Step 4.a. var next = callContentFunction(iterator.next, iterator); if (!IsObject(next)) - ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); + ThrowTypeError(JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next"); // Step 4.b. if (next.done) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index a4cfeb753..fb141e92d 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -58,6 +58,7 @@ using mozilla::Some; class BreakableControl; class LabelControl; class LoopControl; +class ForOfLoopControl; class TryFinallyControl; static bool @@ -150,6 +151,13 @@ BytecodeEmitter::NestableControl::is() const return StatementKindIsLoop(kind_); } +template <> +bool +BytecodeEmitter::NestableControl::is() const +{ + return kind_ == StatementKind::ForOfLoop; +} + template <> bool BytecodeEmitter::NestableControl::is() const @@ -270,6 +278,64 @@ class LoopControl : public BreakableControl } }; +class ForOfLoopControl : public LoopControl +{ + // The stack depth of the iterator. + int32_t iterDepth_; + + // for-of loops, when throwing from non-iterator code (i.e. from the body + // or from evaluating the LHS of the loop condition), need to call + // IteratorClose. If IteratorClose itself throws, we must not re-call + // IteratorClose. Since non-local jumps like break and return call + // IteratorClose, whenever a non-local jump is emitted, we must terminate + // the current JSTRY_ITERCLOSE note to skip the non-local jump code, then + // start a new one. + // + // Visually, + // + // for (x of y) { + // ... instantiate ForOfLoopControl + // ... + <-- iterCloseTryStart_ points to right before + // ... assignment to loop variable + // ... ^ + // ... | + // if (...) v + // + call finishIterCloseTryNote before |break| + // above range is noted with JSTRY_ITERCLOSE + // + // break; <-- break and IteratorClose are not inside + // JSTRY_ITERCLOSE note + // + // call startNewIterCloseTryNote after |break| + // + <-- next iterCloseTryStart_ points here + // ... | + // ... ~ + // } + ptrdiff_t iterCloseTryStart_; + + public: + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth) + : LoopControl(bce, StatementKind::ForOfLoop), + iterDepth_(iterDepth), + iterCloseTryStart_(-1) + { + MOZ_ASSERT(bce->stackDepth >= iterDepth); + } + + MOZ_MUST_USE bool finishIterCloseTryNote(BytecodeEmitter* bce) { + ptrdiff_t end = bce->offset(); + MOZ_ASSERT(end >= iterCloseTryStart_); + if (end != iterCloseTryStart_) + return bce->tryNoteList.append(JSTRY_ITERCLOSE, iterDepth_, iterCloseTryStart_, end); + return true; + } + + void startNewIterCloseTryNote(BytecodeEmitter* bce) { + MOZ_ASSERT(bce->offset() > iterCloseTryStart_); + iterCloseTryStart_ = bce->offset(); + } +}; + class TryFinallyControl : public BytecodeEmitter::NestableControl { bool emittingSubroutine_; @@ -1497,6 +1563,156 @@ BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, return true; } +class MOZ_STACK_CLASS IfThenElseEmitter +{ + BytecodeEmitter* bce_; + JumpList jumpAroundThen_; + JumpList jumpsAroundElse_; + unsigned noteIndex_; + int32_t thenDepth_; +#ifdef DEBUG + int32_t pushed_; + bool calculatedPushed_; +#endif + enum State { + Start, + If, + Cond, + IfElse, + Else, + End + }; + State state_; + + public: + explicit IfThenElseEmitter(BytecodeEmitter* bce) + : bce_(bce), + noteIndex_(-1), + thenDepth_(0), +#ifdef DEBUG + pushed_(0), + calculatedPushed_(false), +#endif + state_(Start) + {} + + ~IfThenElseEmitter() + {} + + private: + bool emitIf(State nextState) { + MOZ_ASSERT(state_ == Start || state_ == Else); + MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond); + + // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ. + if (state_ == Else) + jumpAroundThen_ = JumpList(); + + // Emit an annotated branch-if-false around the then part. + SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND; + if (!bce_->newSrcNote(type, ¬eIndex_)) + return false; + if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) + return false; + + // To restore stack depth in else part, save depth of the then part. +#ifdef DEBUG + // If DEBUG, this is also necessary to calculate |pushed_|. + thenDepth_ = bce_->stackDepth; +#else + if (nextState == IfElse || nextState == Cond) + thenDepth_ = bce_->stackDepth; +#endif + state_ = nextState; + return true; + } + + public: + bool emitIf() { + return emitIf(If); + } + + bool emitCond() { + return emitIf(Cond); + } + + bool emitIfElse() { + return emitIf(IfElse); + } + + bool emitElse() { + MOZ_ASSERT(state_ == IfElse || state_ == Cond); + + calculateOrCheckPushed(); + + // Emit a jump from the end of our then part around the else part. The + // patchJumpsToTarget call at the bottom of this function will fix up + // the offset with jumpsAroundElse value. + if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) + return false; + + // Ensure the branch-if-false comes here, then emit the else. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + return false; + + // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to + // jump, for IonMonkey's benefit. We can't just "back up" from the pc + // of the else clause, because we don't know whether an extended + // jump was required to leap from the end of the then clause over + // the else clause. + if (!bce_->setSrcNoteOffset(noteIndex_, 0, + jumpsAroundElse_.offset - jumpAroundThen_.offset)) + { + return false; + } + + // Restore stack depth of the then part. + bce_->stackDepth = thenDepth_; + state_ = Else; + return true; + } + + bool emitEnd() { + MOZ_ASSERT(state_ == If || state_ == Else); + + calculateOrCheckPushed(); + + if (state_ == If) { + // No else part, fixup the branch-if-false to come here. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + return false; + } + + // Patch all the jumps around else parts. + if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) + return false; + + state_ = End; + return true; + } + + void calculateOrCheckPushed() { +#ifdef DEBUG + if (!calculatedPushed_) { + pushed_ = bce_->stackDepth - thenDepth_; + calculatedPushed_ = true; + } else { + MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); + } +#endif + } + +#ifdef DEBUG + int32_t pushed() const { + return pushed_; + } + + int32_t popped() const { + return -pushed_; + } +#endif +}; + BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, @@ -2013,22 +2229,43 @@ BytecodeEmitter::flushPops(int* npops) namespace { -class NonLocalExitControl { +class NonLocalExitControl +{ + public: + enum Kind + { + // IteratorClose is handled especially inside the exception unwinder. + Throw, + + // A 'continue' statement does not call IteratorClose for the loop it + // is continuing, i.e. excluding the target loop. + Continue, + + // A 'break' or 'return' statement does call IteratorClose for the + // loop it is breaking out of or returning from, i.e. including the + // target loop. + Break, + Return + }; + + private: BytecodeEmitter* bce_; const uint32_t savedScopeNoteIndex_; const int savedDepth_; uint32_t openScopeNoteIndex_; + Kind kind_; NonLocalExitControl(const NonLocalExitControl&) = delete; MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope); public: - explicit NonLocalExitControl(BytecodeEmitter* bce) + NonLocalExitControl(BytecodeEmitter* bce, Kind kind) : bce_(bce), savedScopeNoteIndex_(bce->scopeNoteList.length()), savedDepth_(bce->stackDepth), - openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()) + openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()), + kind_(kind) { } ~NonLocalExitControl() { @@ -2075,6 +2312,14 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta EmitterScope* es = bce_->innermostEmitterScope; int npops = 0; + bool hasForOfLoopsWithIteratorClose = false; + + // IteratorClose is handled specially in the exception unwinder. For + // 'continue', 'break', and 'return' statements, emit IteratorClose + // bytecode inline. 'continue' statements do not call IteratorClose for + // the loop they are continuing. + bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return; + bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue; auto flushPops = [&npops](BytecodeEmitter* bce) { if (npops && !bce->flushPops(&npops)) @@ -2114,15 +2359,29 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } case StatementKind::ForOfLoop: - npops += 2; + // The iterator and the current value are on the stack. + // + if (emitIteratorClose) { + hasForOfLoopsWithIteratorClose = true; + if (!control->as().finishIterCloseTryNote(bce_)) + return false; + if (!bce_->emit1(JSOP_POP)) // ... ITER + return false; + if (!bce_->emitIteratorClose()) // ... + return false; + } else { + if (!bce_->emit1(JSOP_POP)) // ... ITER + return false; + if (!bce_->emit1(JSOP_POP)) // ... + return false; + } break; case StatementKind::ForInLoop: - /* The iterator and the current value are on the stack. */ - npops += 1; - if (!flushPops(bce_)) + // The iterator and the current value are on the stack. + if (!bce_->emit1(JSOP_POP)) // ... ITER return false; - if (!bce_->emit1(JSOP_ENDITER)) + if (!bce_->emit1(JSOP_ENDITER)) // ... return false; break; @@ -2131,13 +2390,45 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } } + if (target && target->is() && emitIteratorCloseAtTarget) { + hasForOfLoopsWithIteratorClose = true; + if (!target->as().finishIterCloseTryNote(bce_)) + return false; + + // The iterator and the current value are on the stack. At the level + // of the target block, there's bytecode after the loop that will pop + // the iterator and the value, so duplicate the iterator and call + // IteratorClose. + if (!bce_->emitDupAt(1)) // ... ITER RESULT ITER + return false; + if (!bce_->emitIteratorClose()) // ... ITER RESULT + return false; + } + EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; for (; es != targetEmitterScope; es = es->enclosingInFrame()) { if (!leaveScope(es)) return false; } - return flushPops(bce_); + if (!flushPops(bce_)) + return false; + + // See comment in ForOfLoopControl. + if (hasForOfLoopsWithIteratorClose) { + for (NestableControl* control = bce_->innermostNestableControl; + control != target; + control = control->enclosing()) + { + if (control->is()) + control->as().startNewIterCloseTryNote(bce_); + } + + if (target && target->is() && emitIteratorCloseAtTarget) + target->as().startNewIterCloseTryNote(bce_); + } + + return true; } } // anonymous namespace @@ -2145,7 +2436,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType) { - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, noteType == SRC_CONTINUE + ? NonLocalExitControl::Continue + : NonLocalExitControl::Break); if (!nle.prepareForNonLocalJump(target)) return false; @@ -4529,6 +4822,144 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) return true; } +bool +BytecodeEmitter::emitIteratorClose(Maybe yieldStarTryStart, bool allowSelfHosted) +{ + MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, + ".close() on iterators is prohibited in self-hosted code because it " + "can run user-modifiable iteration code"); + + // Generate inline logic corresponding to IteratorClose (ES 7.4.6). + // + // Callers need to ensure that the iterator object is at the top of the + // stack. + + if (!emit1(JSOP_DUP)) // ... ITER ITER + return false; + + // Step 3. + // + // Get the "return" method. + if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ... ITER RET + return false; + + // Step 4. + // + // Do nothing if "return" is null or undefined. + IfThenElseEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOP_DUP)) // ... ITER RET RET + return false; + if (!emit1(JSOP_UNDEFINED)) // ... ITER RET RET UNDEFINED + return false; + if (!emit1(JSOP_NE)) // ... ITER RET ?NEQL + return false; + if (!ifReturnMethodIsDefined.emitIfElse()) + return false; + + // Steps 5, 8. + // + // Call "return" if it is not undefined or null, and check that it returns + // an Object. + if (!emit1(JSOP_SWAP)) // ... RET ITER + return false; + + // ES 14.4.13, yield * AssignmentExpression, step 5.c + // + // When emitting iterator.return() for yield* forced return, we need to + // pass the argument passed to Generator.prototype.return to the return + // method. + if (yieldStarTryStart) { + IfThenElseEmitter ifGeneratorClosing(this); + if (!emitDupAt(2)) // ... FTYPE FVALUE RET ITER FVALUE + return false; + if (!emit1(JSOP_ISGENCLOSING)) // ... FTYPE FVALUE RET ITER FVALUE CLOSING + return false; + if (!emit1(JSOP_SWAP)) // ... FTYPE FVALUE RET ITER CLOSING FVALUE + return false; + if (!emit1(JSOP_POP)) // ... FTYPE FVALUE RET ITER CLOSING + return false; + if (!ifGeneratorClosing.emitIfElse()) // ... FTYPE FVALUE RET ITER + return false; + + if (!emit1(JSOP_GETRVAL)) // ... FTYPE FVALUE RET ITER RVAL + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... FTYPE FVALUE RET ITER VALUE + return false; + if (!emitCall(JSOP_CALL, 1)) // ... FTYPE FVALUE RESULT + return false; + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... FTYPE FVALUE RESULT + return false; + + IfThenElseEmitter ifReturnDone(this); + if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RESULT DONE + return false; + if (!ifReturnDone.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT + return false; + if (!emit1(JSOP_SETRVAL)) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!ifReturnDone.emitElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + int32_t savedDepth = this->stackDepth; + if (!emit2(JSOP_UNPICK, 3)) // ITER RESULT OLDRESULT FTYPE FVALUE + return false; + if (!emitUint16Operand(JSOP_POPN, 3)) // ITER RESULT + return false; + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_GOTO, *yieldStarTryStart, &beq, &breakTarget)) // ITER RESULT + return false; + this->stackDepth = savedDepth; + if (!ifReturnDone.emitEnd()) + return false; + + if (!ifGeneratorClosing.emitElse()) // ... FTYPE FVALUE RET ITER + return false; + if (!emitCall(JSOP_CALL, 0)) // ... FTYPE FVALUE RESULT + return false; + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... FTYPE FVALUE RESULT + return false; + + if (!ifGeneratorClosing.emitEnd()) + return false; + } else { + if (!emitCall(JSOP_CALL, 0)) // ... RESULT + return false; + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT + return false; + } + + if (!ifReturnMethodIsDefined.emitElse()) + return false; + if (!emit1(JSOP_POP)) // ... ITER + return false; + if (!ifReturnMethodIsDefined.emitEnd()) + return false; + + return emit1(JSOP_POP); // ... +} + +template +bool +BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter) +{ + MOZ_ASSERT(this->stackDepth >= iterDepth); + + ptrdiff_t start = offset(); + if (!emitter(this)) + return false; + ptrdiff_t end = offset(); + if (start != end) + return tryNoteList.append(JSTRY_ITERCLOSE, iterDepth, start, end); + return true; +} + bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { @@ -4617,156 +5048,6 @@ BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* patt return emitInitializer(initializer, pattern); } -class MOZ_STACK_CLASS IfThenElseEmitter -{ - BytecodeEmitter* bce_; - JumpList jumpAroundThen_; - JumpList jumpsAroundElse_; - unsigned noteIndex_; - int32_t thenDepth_; -#ifdef DEBUG - int32_t pushed_; - bool calculatedPushed_; -#endif - enum State { - Start, - If, - Cond, - IfElse, - Else, - End - }; - State state_; - - public: - explicit IfThenElseEmitter(BytecodeEmitter* bce) - : bce_(bce), - noteIndex_(-1), - thenDepth_(0), -#ifdef DEBUG - pushed_(0), - calculatedPushed_(false), -#endif - state_(Start) - {} - - ~IfThenElseEmitter() - {} - - private: - bool emitIf(State nextState) { - MOZ_ASSERT(state_ == Start || state_ == Else); - MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond); - - // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ. - if (state_ == Else) - jumpAroundThen_ = JumpList(); - - // Emit an annotated branch-if-false around the then part. - SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND; - if (!bce_->newSrcNote(type, ¬eIndex_)) - return false; - if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) - return false; - - // To restore stack depth in else part, save depth of the then part. -#ifdef DEBUG - // If DEBUG, this is also necessary to calculate |pushed_|. - thenDepth_ = bce_->stackDepth; -#else - if (nextState == IfElse || nextState == Cond) - thenDepth_ = bce_->stackDepth; -#endif - state_ = nextState; - return true; - } - - public: - bool emitIf() { - return emitIf(If); - } - - bool emitCond() { - return emitIf(Cond); - } - - bool emitIfElse() { - return emitIf(IfElse); - } - - bool emitElse() { - MOZ_ASSERT(state_ == IfElse || state_ == Cond); - - calculateOrCheckPushed(); - - // Emit a jump from the end of our then part around the else part. The - // patchJumpsToTarget call at the bottom of this function will fix up - // the offset with jumpsAroundElse value. - if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) - return false; - - // Ensure the branch-if-false comes here, then emit the else. - if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) - return false; - - // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to - // jump, for IonMonkey's benefit. We can't just "back up" from the pc - // of the else clause, because we don't know whether an extended - // jump was required to leap from the end of the then clause over - // the else clause. - if (!bce_->setSrcNoteOffset(noteIndex_, 0, - jumpsAroundElse_.offset - jumpAroundThen_.offset)) - { - return false; - } - - // Restore stack depth of the then part. - bce_->stackDepth = thenDepth_; - state_ = Else; - return true; - } - - bool emitEnd() { - MOZ_ASSERT(state_ == If || state_ == Else); - - calculateOrCheckPushed(); - - if (state_ == If) { - // No else part, fixup the branch-if-false to come here. - if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) - return false; - } - - // Patch all the jumps around else parts. - if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) - return false; - - state_ = End; - return true; - } - - void calculateOrCheckPushed() { -#ifdef DEBUG - if (!calculatedPushed_) { - pushed_ = bce_->stackDepth - thenDepth_; - calculatedPushed_ = true; - } else { - MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); - } -#endif - } - -#ifdef DEBUG - int32_t pushed() const { - return pushed_; - } - - int32_t popped() const { - return -pushed_; - } -#endif -}; - bool BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav) { @@ -5710,7 +5991,7 @@ BytecodeEmitter::emitCatch(ParseNode* pn) return false; { - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, NonLocalExitControl::Throw); // Move exception back to cx->exception to prepare for // the next catch. @@ -6303,12 +6584,14 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!emitIterator()) // ITER return false; + int32_t iterDepth = stackDepth; + // For-of loops have both the iterator and the value on the stack. Push // undefined to balance the stack. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; - LoopControl loopInfo(this, StatementKind::ForOfLoop); + ForOfLoopControl loopInfo(this, iterDepth); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; @@ -6354,18 +6637,23 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte #endif // Emit code to assign result.value to the iteration variable. + // + // Note that ES 13.7.5.13, step 5.c says getting result.value does not + // call IteratorClose, so start JSTRY_ITERCLOSE after the GETPROP. if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; + loopInfo.startNewIterCloseTryNote(this); + if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE return false; if (!emit1(JSOP_POP)) // ITER RESULT return false; - MOZ_ASSERT(this->stackDepth == loopDepth, + MOZ_ASSERT(stackDepth == loopDepth, "the stack must be balanced around the initializing " "operation"); @@ -6374,6 +6662,9 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!emitTree(forBody)) // ITER RESULT return false; + if (!loopInfo.finishIterCloseTryNote(this)) + return false; + // Set offset for continues. loopInfo.continueTarget = { offset() }; @@ -7609,7 +7900,7 @@ BytecodeEmitter::emitReturn(ParseNode* pn) return false; } - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, NonLocalExitControl::Return); if (!nle.prepareForNonLocalJumpToOutermost()) return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 066c06672..6b39069de 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -679,6 +679,12 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorClose( + mozilla::Maybe yieldStarTryStart = mozilla::Nothing(), + bool allowSelfHosted = false); + + template + MOZ_MUST_USE bool wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter); // Check if the value on top of the stack is "undefined". If so, replace // that value on the stack with the value defined by |defaultExpr|. diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 646442b4c..d6c6fc4c9 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -328,11 +328,15 @@ NumArgAndLocalSlots(const InlineFrameIterator& frame) } static void -CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, uint32_t stackSlot) +CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn) { + MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || tn->kind == JSTRY_ITERCLOSE); + MOZ_ASSERT(tn->stackDepth > 0); + SnapshotIterator si = frame.snapshotIterator(); // Skip stack slots until we reach the iterator object. + uint32_t stackSlot = tn->stackDepth; uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - 1; for (unsigned i = 0; i < skipSlots; i++) @@ -341,10 +345,14 @@ CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, uint32_t s Value v = si.read(); RootedObject obj(cx, &v.toObject()); - if (cx->isExceptionPending()) - UnwindIteratorForException(cx, obj); - else + if (cx->isExceptionPending()) { + if (tn->kind == JSTRY_FOR_IN) + UnwindIteratorForException(cx, obj); + else + IteratorCloseForException(cx, obj); + } else { UnwindIteratorForUncatchableException(cx, obj); + } } class IonFrameStackDepthOp @@ -417,12 +425,13 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx JSTryNote* tn = *tni; switch (tn->kind) { - case JSTRY_FOR_IN: { - MOZ_ASSERT(JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER); + case JSTRY_FOR_IN: + case JSTRY_ITERCLOSE: { + MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, + JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER); MOZ_ASSERT(tn->stackDepth > 0); - uint32_t localSlot = tn->stackDepth; - CloseLiveIteratorIon(cx, frame, localSlot); + CloseLiveIteratorIon(cx, frame, tn); break; } @@ -598,17 +607,23 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen return true; } - case JSTRY_FOR_IN: { + case JSTRY_FOR_IN: + case JSTRY_ITERCLOSE: { uint8_t* framePointer; uint8_t* stackPointer; BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); - Value iterValue(*(Value*) stackPointer); + Value iterValue(*(reinterpret_cast(stackPointer))); RootedObject iterObject(cx, &iterValue.toObject()); - if (!UnwindIteratorForException(cx, iterObject)) { + bool ok; + if (tn->kind == JSTRY_FOR_IN) + ok = UnwindIteratorForException(cx, iterObject); + else + ok = IteratorCloseForException(cx, iterObject); + if (!ok) { // See comment in the JSTRY_FOR_IN case in Interpreter.cpp's // ProcessTryNotes. SettleOnTryNote(cx, tn, frame, ei, rfe, pc); - MOZ_ASSERT(**pc == JSOP_ENDITER); + MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, **pc == JSOP_ENDITER); return false; } break; diff --git a/js/src/js.msg b/js/src/js.msg index 8766bfbf5..30c0d3035 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -96,7 +96,7 @@ MSG_DEF(JSMSG_NOT_ITERABLE, 1, JSEXN_TYPEERR, "{0} is not iterable") MSG_DEF(JSMSG_NOT_ITERATOR, 1, JSEXN_TYPEERR, "{0} is not iterator") MSG_DEF(JSMSG_ALREADY_HAS_PRAGMA, 2, JSEXN_WARN, "{0} is being assigned a {1}, but already has one") MSG_DEF(JSMSG_GET_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.iterator]() returned a non-object value") -MSG_DEF(JSMSG_NEXT_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "iterator.next() returned a non-object value") +MSG_DEF(JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, 1, JSEXN_TYPEERR, "iterator.{0}() returned a non-object value") MSG_DEF(JSMSG_CANT_SET_PROTO, 0, JSEXN_TYPEERR, "can't set prototype of this object") MSG_DEF(JSMSG_CANT_SET_PROTO_OF, 1, JSEXN_TYPEERR, "can't set prototype of {0}") MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE, 0, JSEXN_TYPEERR, "can't set prototype: it would cause a prototype chain cycle") diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 63c81e6af..c1ae5dc15 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -12,6 +12,7 @@ #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" +#include "mozilla/Unused.h" #include "jsarray.h" #include "jsatom.h" @@ -1229,6 +1230,7 @@ js::CloseIterator(JSContext* cx, HandleObject obj) } return LegacyGeneratorObject::close(cx, obj); } + return true; } @@ -1246,6 +1248,56 @@ js::UnwindIteratorForException(JSContext* cx, HandleObject obj) return true; } +bool +js::IteratorCloseForException(JSContext* cx, HandleObject obj) +{ + MOZ_ASSERT(cx->isExceptionPending()); + + bool isClosingGenerator = cx->isClosingGenerator(); + JS::AutoSaveExceptionState savedExc(cx); + + // Implements IteratorClose (ES 7.4.6) for exception unwinding. See + // also the bytecode generated by BytecodeEmitter::emitIteratorClose. + + // Step 3. + // + // Get the "return" method. + RootedValue returnMethod(cx); + if (!GetProperty(cx, obj, obj, cx->names().return_, &returnMethod)) + return false; + + // Step 4. + // + // Do nothing if "return" is null or undefined. Throw a TypeError if the + // method is not IsCallable. + if (returnMethod.isNullOrUndefined()) + return true; + if (!IsCallable(returnMethod)) + return ReportIsNotFunction(cx, returnMethod); + + // Step 5, 6, 8. + // + // Call "return" if it is not null or undefined. + RootedValue rval(cx); + bool ok = Call(cx, returnMethod, obj, &rval); + if (isClosingGenerator) { + // Closing an iterator is implemented as an exception, but in spec + // terms it is a Completion value with [[Type]] return. In this case + // we *do* care if the call threw and if it returned an object. + if (!ok) + return false; + if (!rval.isObject()) + return ThrowCheckIsObject(cx, CheckIsObjectKind::IteratorReturn); + } else { + // We don't care if the call threw or that it returned an Object, as + // Step 6 says if IteratorClose is being called during a throw, the + // original throw has primacy. + savedExc.restore(); + } + + return true; +} + void js::UnwindIteratorForUncatchableException(JSContext* cx, JSObject* obj) { diff --git a/js/src/jsiter.h b/js/src/jsiter.h index 35d7ef118..f11f09b55 100644 --- a/js/src/jsiter.h +++ b/js/src/jsiter.h @@ -189,6 +189,9 @@ CloseIterator(JSContext* cx, HandleObject iterObj); bool UnwindIteratorForException(JSContext* cx, HandleObject obj); +bool +IteratorCloseForException(JSContext* cx, HandleObject obj); + void UnwindIteratorForUncatchableException(JSContext* cx, JSObject* obj); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 690bc225d..b48d48686 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -84,7 +84,8 @@ enum JSTryNoteKind { JSTRY_FINALLY, JSTRY_FOR_IN, JSTRY_FOR_OF, - JSTRY_LOOP + JSTRY_LOOP, + JSTRY_ITERCLOSE }; /* diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 3f56981cd..294dd935d 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2589,7 +2589,8 @@ JS_STATIC_ASSERT(JSTRY_CATCH == 0); JS_STATIC_ASSERT(JSTRY_FINALLY == 1); JS_STATIC_ASSERT(JSTRY_FOR_IN == 2); -static const char* const TryNoteNames[] = { "catch", "finally", "for-in", "for-of", "loop" }; +static const char* const TryNoteNames[] = { "catch", "finally", "for-in", "for-of", "loop", + "iterclose" }; static MOZ_MUST_USE bool TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) @@ -2597,15 +2598,15 @@ TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) if (!script->hasTrynotes()) return true; - if (sp->put("\nException table:\nkind stack start end\n") < 0) + if (sp->put("\nException table:\nkind stack start end\n") < 0) return false; JSTryNote* tn = script->trynotes()->vector; JSTryNote* tnlimit = tn + script->trynotes()->length; do { MOZ_ASSERT(tn->kind < ArrayLength(TryNoteNames)); - uint8_t startOff = script->pcToOffset(script->main()) + tn->start; - if (!sp->jsprintf(" %-7s %6u %8u %8u\n", + uint32_t startOff = script->pcToOffset(script->main()) + tn->start; + if (!sp->jsprintf(" %-9s %6u %8u %8u\n", TryNoteNames[tn->kind], tn->stackDepth, startOff, startOff + tn->length)) { diff --git a/js/src/tests/ecma_6/Statements/for-of-iterator-close.js b/js/src/tests/ecma_6/Statements/for-of-iterator-close.js new file mode 100644 index 000000000..b28895d8f --- /dev/null +++ b/js/src/tests/ecma_6/Statements/for-of-iterator-close.js @@ -0,0 +1,102 @@ +// Tests that IteratorReturn is called when a for-of loop has an abrupt +// completion value during non-iterator code. + +function test() { + var returnCalled = 0; + var returnCalledExpected = 0; + var iterable = {}; + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + return {}; + } + }); + + // break calls iter.return + for (var x of iterable) + break; + assertEq(returnCalled, ++returnCalledExpected); + + // throw in body calls iter.return + assertThrowsValue(function() { + for (var x of iterable) + throw "in body"; + }, "in body"); + assertEq(returnCalled, ++returnCalledExpected); + + // throw in lhs ref calls iter.return + function throwlhs() { + throw "in lhs"; + } + assertThrowsValue(function() { + for ((throwlhs()) of iterable) + continue; + }, "in lhs"); + assertEq(returnCalled, ++returnCalledExpected); + + // throw in iter.return doesn't re-call iter.return + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + throw "in iter.return"; + } + }); + assertThrowsValue(function() { + for (var x of iterable) + break; + }, "in iter.return"); + assertEq(returnCalled, ++returnCalledExpected); + + // throw in iter.next doesn't call IteratorClose + iterable[Symbol.iterator] = makeIterator({ + next: function() { + throw "in next"; + } + }); + assertThrowsValue(function() { + for (var x of iterable) + break; + }, "in next"); + assertEq(returnCalled, returnCalledExpected); + + // "return" must return an Object. + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + return 42; + } + }); + assertThrowsInstanceOf(function() { + for (var x of iterable) + break; + }, TypeError); + assertEq(returnCalled, ++returnCalledExpected); + + // continue doesn't call iter.return for the loop it's continuing + var i = 0; + iterable[Symbol.iterator] = makeIterator({ + next: function() { + return { done: i++ > 5 }; + }, + ret: function() { + returnCalled++; + return {}; + } + }); + for (var x of iterable) + continue; + assertEq(returnCalled, returnCalledExpected); + + // continue does call iter.return for loops it skips + i = 0; + L: do { + for (var x of iterable) + continue L; + } while (false); + assertEq(returnCalled, ++returnCalledExpected); +} + +test(); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/shell.js b/js/src/tests/ecma_6/shell.js index 06be6f2db..1d067a282 100644 --- a/js/src/tests/ecma_6/shell.js +++ b/js/src/tests/ecma_6/shell.js @@ -3,21 +3,39 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ (function(global) { - /** Yield every permutation of the elements in some array. */ - global.Permutations = function* Permutations(items) { - if (items.length == 0) { - yield []; - } else { - items = items.slice(0); - for (let i = 0; i < items.length; i++) { - let swap = items[0]; - items[0] = items[i]; - items[i] = swap; - for (let e of Permutations(items.slice(1, items.length))) - yield [items[0]].concat(e); - } - } - }; + /** Yield every permutation of the elements in some array. */ + global.Permutations = function* Permutations(items) { + if (items.length == 0) { + yield []; + } else { + items = items.slice(0); + for (let i = 0; i < items.length; i++) { + let swap = items[0]; + items[0] = items[i]; + items[i] = swap; + for (let e of Permutations(items.slice(1, items.length))) + yield [items[0]].concat(e); + } + } + }; + + /** Make an iterator with a return method. */ + global.makeIterator = function makeIterator(overrides) { + var iterator = { + next: function() { + if (overrides && overrides.next) + return overrides.next(); + return { done: false }; + }, + return: function() { + if (overrides && overrides.ret) + return overrides.ret(); + return { done: true }; + } + }; + + return function() { return iterator; }; + }; })(this); if (typeof assertThrowsInstanceOf === 'undefined') { diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 8ae9c43b0..489d0a155 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1171,13 +1171,19 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) SettleOnTryNote(cx, tn, ei, regs); return FinallyContinuation; - case JSTRY_FOR_IN: { + case JSTRY_FOR_IN: + case JSTRY_ITERCLOSE: { /* This is similar to JSOP_ENDITER in the interpreter loop. */ DebugOnly pc = regs.fp()->script()->main() + tn->start + tn->length; - MOZ_ASSERT(JSOp(*pc) == JSOP_ENDITER); + MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, JSOp(*pc) == JSOP_ENDITER); Value* sp = regs.spForStackDepth(tn->stackDepth); RootedObject obj(cx, &sp[-1].toObject()); - if (!UnwindIteratorForException(cx, obj)) { + bool ok; + if (tn->kind == JSTRY_FOR_IN) + ok = UnwindIteratorForException(cx, obj); + else + ok = IteratorCloseForException(cx, obj); + if (!ok) { // We should only settle on the note only if // UnwindIteratorForException itself threw, as // onExceptionUnwind should be called anew with the new @@ -5040,7 +5046,12 @@ js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind) { switch (kind) { case CheckIsObjectKind::IteratorNext: - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NEXT_RETURNED_PRIMITIVE); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next"); + break; + case CheckIsObjectKind::IteratorReturn: + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "return"); break; case CheckIsObjectKind::GetIterator: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_GET_ITER_RETURNED_PRIMITIVE); diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 1ffe1fdca..3a24240fc 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -562,6 +562,7 @@ ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, const char* r enum class CheckIsObjectKind : uint8_t { IteratorNext, + IteratorReturn, GetIterator }; -- cgit v1.2.3 From 2d2a60cdae0fb5ac13eb544e54495f54ac886c6c Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 12:11:23 +0100 Subject: Bug 1147371: Rename allowContentSpread to allowContentIter Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 30 ++++++++++++++++++++---------- js/src/frontend/BytecodeEmitter.h | 2 +- js/src/vm/CommonPropertyNames.h | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index fb141e92d..2934f63a5 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -6676,7 +6676,17 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!emit1(JSOP_DUP)) // ITER ITER return false; - if (!emitIteratorNext(forOfHead)) // ITER RESULT + // Certain builtins (e.g. Array.from) are implemented in self-hosting + // as for-of loops. + bool allowSelfHostedIter = false; + if (emitterMode == BytecodeEmitter::SelfHosting && + forHeadExpr->isKind(PNK_CALL) && + forHeadExpr->pn_head->name() == cx->names().allowContentIter) + { + allowSelfHostedIter = true; + } + + if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER RESULT return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; @@ -8438,10 +8448,10 @@ BytecodeEmitter::emitSelfHostedForceInterpreter(ParseNode* pn) } bool -BytecodeEmitter::emitSelfHostedAllowContentSpread(ParseNode* pn) +BytecodeEmitter::emitSelfHostedAllowContentIter(ParseNode* pn) { if (pn->pn_count != 2) { - reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentSpread", "1", ""); + reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentIter", "1", ""); return false; } @@ -8467,7 +8477,7 @@ BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result) if (!pn->isKind(PNK_NAME)) { if (emitterMode == BytecodeEmitter::SelfHosting && pn->isKind(PNK_CALL)) { ParseNode* pn2 = pn->pn_head; - if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentSpread) + if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentIter) return isRestParameter(pn2->pn_next, result); } *result = false; @@ -8575,8 +8585,8 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) return emitSelfHostedResumeGenerator(pn); if (pn2->name() == cx->names().forceInterpreter) return emitSelfHostedForceInterpreter(pn); - if (pn2->name() == cx->names().allowContentSpread) - return emitSelfHostedAllowContentSpread(pn); + if (pn2->name() == cx->names().allowContentIter) + return emitSelfHostedAllowContentIter(pn); // Fall through. } if (!emitGetName(pn2, callop)) @@ -9231,7 +9241,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) if (!updateSourceCoordNotes(pn2->pn_pos.begin)) return false; - bool allowSelfHostedSpread = false; + bool allowSelfHostedIter = false; if (pn2->isKind(PNK_ELISION)) { if (!emit1(JSOP_HOLE)) return false; @@ -9242,9 +9252,9 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) if (emitterMode == BytecodeEmitter::SelfHosting && expr->isKind(PNK_CALL) && - expr->pn_head->name() == cx->names().allowContentSpread) + expr->pn_head->name() == cx->names().allowContentIter) { - allowSelfHostedSpread = true; + allowSelfHostedIter = true; } } else { expr = pn2; @@ -9259,7 +9269,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) return false; if (!emit2(JSOP_PICK, 2)) // ITER ARRAY INDEX return false; - if (!emitSpread(allowSelfHostedSpread)) // ARRAY INDEX + if (!emitSpread(allowSelfHostedIter)) // ARRAY INDEX return false; } else if (afterSpread) { if (!emit1(JSOP_INITELEM_INC)) diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 6b39069de..156abedbe 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -732,7 +732,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn); - MOZ_MUST_USE bool emitSelfHostedAllowContentSpread(ParseNode* pn); + MOZ_MUST_USE bool emitSelfHostedAllowContentIter(ParseNode* pn); MOZ_MUST_USE bool emitComprehensionFor(ParseNode* compFor); MOZ_MUST_USE bool emitComprehensionForIn(ParseNode* pn); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index bd0705446..e971dc844 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -13,7 +13,7 @@ #define FOR_EACH_COMMON_PROPERTYNAME(macro) \ macro(add, add, "add") \ - macro(allowContentSpread, allowContentSpread, "allowContentSpread") \ + macro(allowContentIter, allowContentIter, "allowContentIter") \ macro(anonymous, anonymous, "anonymous") \ macro(Any, Any, "Any") \ macro(apply, apply, "apply") \ -- cgit v1.2.3 From 4b487efb58bfba4c3f67d898e86b9f6daaab59b2 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 12:15:02 +0100 Subject: Bug 1147371: Convert self-hosted code that need to call IteratorClose to use for-of Issue #74 --- js/src/builtin/Array.js | 46 ++++++++++++--------------------------------- js/src/builtin/Classes.js | 2 +- js/src/builtin/Map.js | 38 ++++--------------------------------- js/src/builtin/Set.js | 36 +++-------------------------------- js/src/builtin/Utilities.js | 23 ----------------------- js/src/builtin/WeakMap.js | 38 ++++--------------------------------- js/src/builtin/WeakSet.js | 36 +++-------------------------------- 7 files changed, 27 insertions(+), 192 deletions(-) (limited to 'js/src') diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 5ab0b71be..45f90a7b8 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -803,54 +803,32 @@ function ArrayFrom(items, mapfn=undefined, thisArg=undefined) { // Steps 5.a-c. var A = IsConstructor(C) ? new C() : []; - // Step 5.c. - var iterator = GetIterator(items, usingIterator); - // Step 5.d. var k = 0; - // Step 5.e. - // These steps cannot be implemented using a for-of loop. - // See . - while (true) { + // Step 5.c, 5.e. + var iteratorWrapper = { [std_iterator]() { return GetIterator(items, usingIterator); } }; + for (var nextValue of allowContentIter(iteratorWrapper)) { // Step 5.e.i. // Disabled for performance reason. We won't hit this case on // normal array, since _DefineDataProperty will throw before it. // We could hit this when |A| is a proxy and it ignores // |_DefineDataProperty|, but it happens only after too long loop. /* - if (k >= 0x1fffffffffffff) { - IteratorCloseThrow(iterator); + if (k >= 0x1fffffffffffff) ThrowTypeError(JSMSG_TOO_LONG_ARRAY); - } */ - // Step 5.e.iii. - var next = callContentFunction(iterator.next, iterator); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); - - // Step 5.e.iv. - if (next.done) { - A.length = k; - return A; - } - - // Steps 5.e.v. - var nextValue = next.value; - // Steps 5.e.vi-vii. - try { - var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue; - - // Steps 5.e.ii (reordered), 5.e.viii. - _DefineDataProperty(A, k++, mappedValue); - } catch (e) { - // Steps 5.e.vi.2, 5.e.ix. - IteratorCloseThrow(iterator); - throw e; - } + var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue; + + // Steps 5.e.ii (reordered), 5.e.viii. + _DefineDataProperty(A, k++, mappedValue); } + + // Step 5.e.iv. + A.length = k; + return A; } // Step 7. diff --git a/js/src/builtin/Classes.js b/js/src/builtin/Classes.js index d0f20b5fd..24841d605 100644 --- a/js/src/builtin/Classes.js +++ b/js/src/builtin/Classes.js @@ -5,7 +5,7 @@ var DefaultDerivedClassConstructor = class extends null { constructor(...args) { - super(...allowContentSpread(args)); + super(...allowContentIter(args)); } }; MakeDefaultConstructor(DefaultDerivedClassConstructor); diff --git a/js/src/builtin/Map.js b/js/src/builtin/Map.js index 27a12bfff..580629a13 100644 --- a/js/src/builtin/Map.js +++ b/js/src/builtin/Map.js @@ -14,44 +14,14 @@ function MapConstructorInit(iterable) { if (!IsCallable(adder)) ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder); - // Step 6.c. - var iterFn = iterable[std_iterator]; - if (!IsCallable(iterFn)) - ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable)); - - var iter = callContentFunction(iterFn, iterable); - if (!IsObject(iter)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter); - - // Step 7 (not applicable). - - // Step 8. - while (true) { - // Step 8.a. - var next = callContentFunction(iter.next, iter); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next); - - // Step 8.b. - if (next.done) - return; - - // Step 8.c. - var nextItem = next.value; - + // Steps 6.c-8. + for (var nextItem of allowContentIter(iterable)) { // Step 8.d. - if (!IsObject(nextItem)) { - IteratorCloseThrow(iter); + if (!IsObject(nextItem)) ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Map"); - } // Steps 8.e-j. - try { - callContentFunction(adder, map, nextItem[0], nextItem[1]); - } catch (e) { - IteratorCloseThrow(iter); - throw e; - } + callContentFunction(adder, map, nextItem[0], nextItem[1]); } } diff --git a/js/src/builtin/Set.js b/js/src/builtin/Set.js index c61a49ef8..9af6cf8d1 100644 --- a/js/src/builtin/Set.js +++ b/js/src/builtin/Set.js @@ -14,39 +14,9 @@ function SetConstructorInit(iterable) { if (!IsCallable(adder)) ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder); - // Step 6.c. - var iterFn = iterable[std_iterator]; - if (!IsCallable(iterFn)) - ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable)); - - var iter = callContentFunction(iterFn, iterable); - if (!IsObject(iter)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter); - - // Step 7 (not applicable). - - // Step 8. - while (true) { - // Step 8.a. - var next = callContentFunction(iter.next, iter); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next); - - // Step 8.b. - if (next.done) - return; - - // Step 8.c. - var nextValue = next.value; - - // Steps 8.d-e. - try { - callContentFunction(adder, set, nextValue); - } catch (e) { - IteratorCloseThrow(iter); - throw e; - } - } + // Steps 6.c-8. + for (var nextValue of allowContentIter(iterable)) + callContentFunction(adder, set, nextValue); } /* ES6 20121122 draft 15.16.4.6. */ diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 2dece3801..bfb1fe7f4 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -154,29 +154,6 @@ function GetIterator(obj, method) { return iterator; } -// ES2017 draft rev 7.4.6. -// When completion.[[Type]] is throw. -function IteratorCloseThrow(iter) { - // Steps 1-2 (implicit) - - // Step 3. - var returnMethod = GetMethod(iter, "return"); - - // Step 4 (done in caller). - if (returnMethod === undefined) - return; - - try { - // Step 5. - callContentFunction(returnMethod, iter); - } catch (e) { - } - - // Step 6 (done in caller). - - // Steps 7-9 (skipped). -} - var _builtinCtorsCache = {__proto__: null}; function GetBuiltinConstructor(builtinName) { diff --git a/js/src/builtin/WeakMap.js b/js/src/builtin/WeakMap.js index 708be8424..6755b7a7b 100644 --- a/js/src/builtin/WeakMap.js +++ b/js/src/builtin/WeakMap.js @@ -14,43 +14,13 @@ function WeakMapConstructorInit(iterable) { if (!IsCallable(adder)) ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder); - // Step 6.c. - var iterFn = iterable[std_iterator]; - if (!IsCallable(iterFn)) - ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable)); - - var iter = callContentFunction(iterFn, iterable); - if (!IsObject(iter)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter); - - // Step 7 (not applicable). - - // Step 8. - while (true) { - // Step 8.a. - var next = callContentFunction(iter.next, iter); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next); - - // Step 8.b. - if (next.done) - return; - - // Step 8.c. - var nextItem = next.value; - + // Steps 6.c-8. + for (var nextItem of allowContentIter(iterable)) { // Step 8.d. - if (!IsObject(nextItem)) { - IteratorCloseThrow(iter); + if (!IsObject(nextItem)) ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "WeakMap"); - } // Steps 8.e-j. - try { - callContentFunction(adder, map, nextItem[0], nextItem[1]); - } catch (e) { - IteratorCloseThrow(iter); - throw e; - } + callContentFunction(adder, map, nextItem[0], nextItem[1]); } } diff --git a/js/src/builtin/WeakSet.js b/js/src/builtin/WeakSet.js index 8589f9dc6..b16b4634d 100644 --- a/js/src/builtin/WeakSet.js +++ b/js/src/builtin/WeakSet.js @@ -14,39 +14,9 @@ function WeakSetConstructorInit(iterable) { if (!IsCallable(adder)) ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder); - // Step 6.c. - var iterFn = iterable[std_iterator]; - if (!IsCallable(iterFn)) - ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable)); - - var iter = callContentFunction(iterFn, iterable); - if (!IsObject(iter)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter); - - // Step 7 (not applicable). - - // Step 8. - while (true) { - // Step 8.a. - var next = callContentFunction(iter.next, iter); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next); - - // Step 8.b. - if (next.done) - return; - - // Step 8.c. - var nextValue = next.value; - - // Steps 8.d-e. - try { - callContentFunction(adder, set, nextValue); - } catch (e) { - IteratorCloseThrow(iter); - throw e; - } - } + // Steps 6.c-8. + for (var nextValue of allowContentIter(iterable)) + callContentFunction(adder, set, nextValue); } // 23.4.3.1 -- cgit v1.2.3 From 2bb0252ab48a97a72c33cef9cbe54e86563f15c9 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 12:23:14 +0100 Subject: Bug 1147371: Implement IteratorClose for array destructuring Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 218 ++++++++++++--------- js/src/frontend/BytecodeEmitter.h | 3 +- js/src/jit/JitFrames.cpp | 82 +++++--- js/src/jsscript.h | 3 +- js/src/shell/js.cpp | 35 +++- .../ecma_6/Destructuring/array-iterator-close.js | 77 ++++++++ js/src/vm/Interpreter.cpp | 39 +++- 7 files changed, 322 insertions(+), 135 deletions(-) create mode 100644 js/src/tests/ecma_6/Destructuring/array-iterator-close.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 2934f63a5..e2c906850 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4947,7 +4947,7 @@ BytecodeEmitter::emitIteratorClose(Maybe yieldStarTryStart, bool all template bool -BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter) +BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter) { MOZ_ASSERT(this->stackDepth >= iterDepth); @@ -4956,7 +4956,7 @@ BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter em return false; ptrdiff_t end = offset(); if (start != end) - return tryNoteList.append(JSTRY_ITERCLOSE, iterDepth, start, end); + return tryNoteList.append(JSTRY_DESTRUCTURING_ITERCLOSE, iterDepth, start, end); return true; } @@ -5057,6 +5057,9 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // Here's pseudo code for |let [a, b, , c=y, ...d] = x;| // + // Lines that are annotated "covered by trynote" mean that upon throwing + // an exception, IteratorClose is called on iter only if done is false. + // // let x, y; // let a, b, c, d; // let iter, lref, result, done, value; // stack values @@ -5064,7 +5067,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // iter = x[Symbol.iterator](); // // // ==== emitted by loop for a ==== - // lref = GetReference(a); + // lref = GetReference(a); // covered by trynote // // result = iter.next(); // done = result.done; @@ -5074,10 +5077,10 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // else // value = result.value; // - // SetOrInitialize(lref, value); + // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for b ==== - // lref = GetReference(b); + // lref = GetReference(b); // covered by trynote // // if (done) { // value = undefined; @@ -5090,7 +5093,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // value = result.value; // } // - // SetOrInitialize(lref, value); + // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for elision ==== // if (done) { @@ -5105,7 +5108,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // } // // // ==== emitted by loop for c ==== - // lref = GetReference(c); + // lref = GetReference(c); // covered by trynote // // if (done) { // value = undefined; @@ -5119,19 +5122,23 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // } // // if (value === undefined) - // value = y; + // value = y; // covered by trynote // - // SetOrInitialize(lref, value); + // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for d ==== - // lref = GetReference(d); + // lref = GetReference(d); // covered by trynote // // if (done) // value = []; // else // value = [...iter]; // - // SetOrInitialize(lref, value); + // SetOrInitialize(lref, value); // covered by trynote + // + // // === emitted after loop === + // if (!done) + // IteratorClose(iter); // Use an iterator to destructure the RHS, instead of index lookup. We // must leave the *original* value on the stack. @@ -5140,25 +5147,61 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav if (!emitIterator()) // ... OBJ ITER return false; + // For an empty pattern [], call IteratorClose unconditionally. Nothing + // else needs to be done. + if (!pattern->pn_head) + return emitIteratorClose(); // ... OBJ + + // Push an initial FALSE value for DONE. + if (!emit1(JSOP_FALSE)) // ... OBJ ITER FALSE + return false; + + // JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value + // to be the second to top and the top of the stack, respectively. + // IteratorClose is called upon exception only if done is false. + int32_t tryNoteDepth = stackDepth; + for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { - bool isHead = member == pattern->pn_head; - bool hasNext = !!member->pn_next; + bool isFirst = member == pattern->pn_head; + DebugOnly hasNext = !!member->pn_next; - if (member->isKind(PNK_SPREAD)) { - size_t emitted = 0; - if (!emitDestructuringLHSRef(member, &emitted)) // ... OBJ ITER ?DONE *LREF + size_t emitted = 0; + + // Spec requires LHS reference to be evaluated first. + ParseNode* lhsPattern = member; + if (lhsPattern->isKind(PNK_ASSIGN)) + lhsPattern = lhsPattern->pn_left; + + bool isElision = lhsPattern->isKind(PNK_ELISION); + if (!isElision) { + auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) { + return bce->emitDestructuringLHSRef(lhsPattern, &emitted); // ... OBJ ITER DONE *LREF + }; + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitLHSRef)) + return false; + } + + // Pick the DONE value to the top of the stack. + if (emitted) { + if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE return false; + } + if (isFirst) { + // If this element is the first, DONE is always FALSE, so pop it. + // + // Non-first elements should emit if-else depending on the + // member pattern, below. + if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF + return false; + } + + if (member->isKind(PNK_SPREAD)) { IfThenElseEmitter ifThenElse(this); - if (!isHead) { + if (!isFirst) { // If spread is not the first element of the pattern, // iterator can already be completed. - // ... OBJ ITER DONE *LREF - if (emitted) { - if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE - return false; - } - + // ... OBJ ITER *LREF DONE if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF return false; @@ -5181,13 +5224,22 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY return false; - if (!isHead) { + if (!isFirst) { if (!ifThenElse.emitEnd()) return false; MOZ_ASSERT(ifThenElse.pushed() == 1); } - if (!emitSetOrInitializeDestructuring(member, flav)) // ... OBJ ITER + // At this point the iterator is done. Unpick a TRUE value for DONE above ITER. + if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF ARRAY TRUE + return false; + if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF ARRAY + return false; + + auto emitAssignment = [member, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(member, flav); // ... OBJ ITER TRUE + }; + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) return false; MOZ_ASSERT(!hasNext); @@ -5195,63 +5247,30 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } ParseNode* pndefault = nullptr; - ParseNode* subpattern = member; - if (subpattern->isKind(PNK_ASSIGN)) { - pndefault = subpattern->pn_right; - subpattern = subpattern->pn_left; - } - - bool isElision = subpattern->isKind(PNK_ELISION); - - MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); + if (member->isKind(PNK_ASSIGN)) + pndefault = member->pn_right; - size_t emitted = 0; - if (!isElision) { - if (!emitDestructuringLHSRef(subpattern, &emitted)) // ... OBJ ITER ?DONE *LREF - return false; - } + MOZ_ASSERT(!member->isKind(PNK_SPREAD)); IfThenElseEmitter ifAlreadyDone(this); - if (!isHead) { - // If this element is not the first element of the pattern, - // iterator can already be completed. - // ... OBJ ITER DONE *LREF - if (emitted) { - if (hasNext) { - if (!emitDupAt(emitted)) // ... OBJ ITER DONE *LREF DONE - return false; - } else { - if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE - return false; - } - } else { - if (hasNext) { - // The position of LREF in the following stack comment - // isn't accurate for the operation, but it's equivalent - // since LREF is nothing - if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE *LREF DONE - return false; - } - } - if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF + if (!isFirst) { + // ... OBJ ITER *LREF DONE + if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER *LREF return false; - if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER *LREF UNDEF return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER *LREF UNDEF return false; - if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE *LREF + // The iterator is done. Unpick a TRUE value for DONE above ITER. + if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF UNDEF TRUE + return false; + if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF UNDEF return false; - if (hasNext) { - if (emitted) { - if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE - return false; - } - if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF - return false; - } + if (!ifAlreadyDone.emitElse()) // ... OBJ ITER *LREF + return false; } if (emitted) { @@ -5268,59 +5287,74 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE return false; - if (hasNext) { - if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE - return false; - if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE - return false; - } + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE + return false; + if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE + return false; IfThenElseEmitter ifDone(this); - if (!ifDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF RESULT + if (!ifDone.emitIfElse()) // ... OBJ ITER DONE *LREF RESULT return false; - if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE *LREF + if (!emit1(JSOP_POP)) // ... OBJ ITER DONE *LREF return false; - if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER DONE *LREF UNDEF return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER DONE *LREF UNDEF return false; - if (!ifDone.emitElse()) // ... OBJ ITER ?DONE *LREF RESULT + if (!ifDone.emitElse()) // ... OBJ ITER DONE *LREF RESULT return false; - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE *LREF VALUE + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER DONE *LREF VALUE return false; if (!ifDone.emitEnd()) return false; MOZ_ASSERT(ifDone.pushed() == 0); - if (!isHead) { + if (!isFirst) { if (!ifAlreadyDone.emitEnd()) return false; - MOZ_ASSERT(ifAlreadyDone.pushed() == 1); + MOZ_ASSERT(ifAlreadyDone.pushed() == 2); } if (pndefault) { - if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE *LREF VALUE + auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) { + return bce->emitDefault(pndefault, lhsPattern); // ... OBJ ITER DONE *LREF VALUE + }; + + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault)) return false; } if (!isElision) { - if (!emitSetOrInitializeDestructuring(subpattern, - flav)) // ... OBJ ITER ?DONE - { + auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ ITER DONE + }; + + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) return false; - } } else { - if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE + if (!emit1(JSOP_POP)) // ... OBJ ITER DONE return false; } } + // The last DONE value is on top of the stack. If not DONE, call + // IteratorClose. + // ... OBJ ITER DONE + IfThenElseEmitter ifDone(this); + if (!ifDone.emitIfElse()) // ... OBJ ITER + return false; if (!emit1(JSOP_POP)) // ... OBJ return false; + if (!ifDone.emitElse()) // ... OBJ ITER + return false; + if (!emitIteratorClose()) // ... OBJ + return false; + if (!ifDone.emitEnd()) + return false; return true; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 156abedbe..78eb3510c 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -684,7 +684,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter bool allowSelfHosted = false); template - MOZ_MUST_USE bool wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter); + MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, + InnerEmitter emitter); // Check if the value on top of the stack is "undefined". If so, replace // that value on the stack with the value defined by |defaultExpr|. diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index d6c6fc4c9..30bbd0b9b 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -330,28 +330,44 @@ NumArgAndLocalSlots(const InlineFrameIterator& frame) static void CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn) { - MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || tn->kind == JSTRY_ITERCLOSE); - MOZ_ASSERT(tn->stackDepth > 0); + MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || + tn->kind == JSTRY_ITERCLOSE || + tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE); + + bool isDestructuring = tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE; + MOZ_ASSERT_IF(!isDestructuring, tn->stackDepth > 0); + MOZ_ASSERT_IF(isDestructuring, tn->stackDepth > 1); SnapshotIterator si = frame.snapshotIterator(); - // Skip stack slots until we reach the iterator object. + // Skip stack slots until we reach the iterator object on the stack. For + // the destructuring case, we also need to get the "done" value. uint32_t stackSlot = tn->stackDepth; - uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - 1; + uint32_t adjust = isDestructuring ? 2 : 1; + uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - adjust; for (unsigned i = 0; i < skipSlots; i++) si.skip(); Value v = si.read(); - RootedObject obj(cx, &v.toObject()); + RootedObject iterObject(cx, &v.toObject()); + + if (isDestructuring) { + Value v = si.read(); + MOZ_ASSERT(v.isBoolean()); + // Do not call IteratorClose if the destructuring iterator is already + // done. + if (v.isTrue()) + return; + } if (cx->isExceptionPending()) { if (tn->kind == JSTRY_FOR_IN) - UnwindIteratorForException(cx, obj); + UnwindIteratorForException(cx, iterObject); else - IteratorCloseForException(cx, obj); + IteratorCloseForException(cx, iterObject); } else { - UnwindIteratorForUncatchableException(cx, obj); + UnwindIteratorForUncatchableException(cx, iterObject); } } @@ -426,14 +442,12 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx switch (tn->kind) { case JSTRY_FOR_IN: - case JSTRY_ITERCLOSE: { + case JSTRY_ITERCLOSE: + case JSTRY_DESTRUCTURING_ITERCLOSE: MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER); - MOZ_ASSERT(tn->stackDepth > 0); - CloseLiveIteratorIon(cx, frame, tn); break; - } case JSTRY_FOR_OF: case JSTRY_LOOP: @@ -607,28 +621,52 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen return true; } - case JSTRY_FOR_IN: - case JSTRY_ITERCLOSE: { + case JSTRY_FOR_IN: { uint8_t* framePointer; uint8_t* stackPointer; BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); - Value iterValue(*(reinterpret_cast(stackPointer))); + Value iterValue(*reinterpret_cast(stackPointer)); RootedObject iterObject(cx, &iterValue.toObject()); - bool ok; - if (tn->kind == JSTRY_FOR_IN) - ok = UnwindIteratorForException(cx, iterObject); - else - ok = IteratorCloseForException(cx, iterObject); - if (!ok) { + if (!UnwindIteratorForException(cx, iterObject)) { // See comment in the JSTRY_FOR_IN case in Interpreter.cpp's // ProcessTryNotes. SettleOnTryNote(cx, tn, frame, ei, rfe, pc); - MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, **pc == JSOP_ENDITER); + MOZ_ASSERT(**pc == JSOP_ENDITER); + return false; + } + break; + } + + case JSTRY_ITERCLOSE: { + uint8_t* framePointer; + uint8_t* stackPointer; + BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); + Value iterValue(*reinterpret_cast(stackPointer)); + RootedObject iterObject(cx, &iterValue.toObject()); + if (!IteratorCloseForException(cx, iterObject)) { + SettleOnTryNote(cx, tn, frame, ei, rfe, pc); return false; } break; } + case JSTRY_DESTRUCTURING_ITERCLOSE: { + uint8_t* framePointer; + uint8_t* stackPointer; + BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); + Value doneValue(*(reinterpret_cast(stackPointer))); + MOZ_ASSERT(doneValue.isBoolean()); + if (doneValue.isFalse()) { + Value iterValue(*(reinterpret_cast(stackPointer) + 1)); + RootedObject iterObject(cx, &iterValue.toObject()); + if (!IteratorCloseForException(cx, iterObject)) { + SettleOnTryNote(cx, tn, frame, ei, rfe, pc); + return false; + } + } + break; + } + case JSTRY_FOR_OF: case JSTRY_LOOP: break; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index b48d48686..8bba3ec39 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -85,7 +85,8 @@ enum JSTryNoteKind { JSTRY_FOR_IN, JSTRY_FOR_OF, JSTRY_LOOP, - JSTRY_ITERCLOSE + JSTRY_ITERCLOSE, + JSTRY_DESTRUCTURING_ITERCLOSE }; /* diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 294dd935d..6fe01de22 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2585,12 +2585,28 @@ Notes(JSContext* cx, unsigned argc, Value* vp) return true; } -JS_STATIC_ASSERT(JSTRY_CATCH == 0); -JS_STATIC_ASSERT(JSTRY_FINALLY == 1); -JS_STATIC_ASSERT(JSTRY_FOR_IN == 2); +static const char* +TryNoteName(JSTryNoteKind kind) +{ + switch (kind) { + case JSTRY_CATCH: + return "catch"; + case JSTRY_FINALLY: + return "finally"; + case JSTRY_FOR_IN: + return "for-in"; + case JSTRY_FOR_OF: + return "for-of"; + case JSTRY_LOOP: + return "loop"; + case JSTRY_ITERCLOSE: + return "iterclose"; + case JSTRY_DESTRUCTURING_ITERCLOSE: + return "dstr-iterclose"; + } -static const char* const TryNoteNames[] = { "catch", "finally", "for-in", "for-of", "loop", - "iterclose" }; + MOZ_CRASH("Bad JSTryNoteKind"); +} static MOZ_MUST_USE bool TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) @@ -2598,17 +2614,16 @@ TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) if (!script->hasTrynotes()) return true; - if (sp->put("\nException table:\nkind stack start end\n") < 0) + if (sp->put("\nException table:\nkind stack start end\n") < 0) return false; JSTryNote* tn = script->trynotes()->vector; JSTryNote* tnlimit = tn + script->trynotes()->length; do { - MOZ_ASSERT(tn->kind < ArrayLength(TryNoteNames)); uint32_t startOff = script->pcToOffset(script->main()) + tn->start; - if (!sp->jsprintf(" %-9s %6u %8u %8u\n", - TryNoteNames[tn->kind], tn->stackDepth, - startOff, startOff + tn->length)) + if (!sp->jsprintf(" %-14s %6u %8u %8u\n", + TryNoteName(static_cast(tn->kind)), + tn->stackDepth, startOff, startOff + tn->length)) { return false; } diff --git a/js/src/tests/ecma_6/Destructuring/array-iterator-close.js b/js/src/tests/ecma_6/Destructuring/array-iterator-close.js new file mode 100644 index 000000000..f7805540d --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/array-iterator-close.js @@ -0,0 +1,77 @@ +// Tests that IteratorClose is called in array destructuring patterns. + +function test() { + var returnCalled = 0; + var returnCalledExpected = 0; + var iterable = {}; + + // empty [] calls IteratorClose regardless of "done" on the result. + iterable[Symbol.iterator] = makeIterator({ + next: function() { + return { done: true }; + }, + ret: function() { + returnCalled++; + return {}; + } + }); + var [] = iterable; + assertEq(returnCalled, ++returnCalledExpected); + + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + return {}; + } + }); + var [] = iterable; + assertEq(returnCalled, ++returnCalledExpected); + + // Non-empty destructuring calls IteratorClose if iterator is not done by + // the end of destructuring. + var [a,b] = iterable; + assertEq(returnCalled, ++returnCalledExpected); + var [c,] = iterable; + assertEq(returnCalled, ++returnCalledExpected); + + // throw in lhs ref calls IteratorClose + function throwlhs() { + throw "in lhs"; + } + assertThrowsValue(function() { + 0, [...{}[throwlhs()]] = iterable; + }, "in lhs"); + assertEq(returnCalled, ++returnCalledExpected); + + // throw in iter.next doesn't call IteratorClose + iterable[Symbol.iterator] = makeIterator({ + next: function() { + throw "in next"; + }, + ret: function() { + returnCalled++; + return {}; + } + }); + assertThrowsValue(function() { + var [d] = iterable; + }, "in next"); + assertEq(returnCalled, returnCalledExpected); + + // "return" must return an Object. + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + return 42; + } + }); + assertThrowsInstanceOf(function() { + var [] = iterable; + }, TypeError); + assertEq(returnCalled, ++returnCalledExpected); +} + +test(); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 489d0a155..923c824ce 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1171,19 +1171,13 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) SettleOnTryNote(cx, tn, ei, regs); return FinallyContinuation; - case JSTRY_FOR_IN: - case JSTRY_ITERCLOSE: { + case JSTRY_FOR_IN: { /* This is similar to JSOP_ENDITER in the interpreter loop. */ DebugOnly pc = regs.fp()->script()->main() + tn->start + tn->length; - MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, JSOp(*pc) == JSOP_ENDITER); + MOZ_ASSERT(JSOp(*pc) == JSOP_ENDITER); Value* sp = regs.spForStackDepth(tn->stackDepth); RootedObject obj(cx, &sp[-1].toObject()); - bool ok; - if (tn->kind == JSTRY_FOR_IN) - ok = UnwindIteratorForException(cx, obj); - else - ok = IteratorCloseForException(cx, obj); - if (!ok) { + if (!UnwindIteratorForException(cx, obj)) { // We should only settle on the note only if // UnwindIteratorForException itself threw, as // onExceptionUnwind should be called anew with the new @@ -1195,6 +1189,33 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) break; } + case JSTRY_ITERCLOSE: { + // The iterator object is at the top of the stack. + Value* sp = regs.spForStackDepth(tn->stackDepth); + RootedObject iterObject(cx, &sp[-1].toObject()); + if (!IteratorCloseForException(cx, iterObject)) { + SettleOnTryNote(cx, tn, ei, regs); + return ErrorReturnContinuation; + } + break; + } + + case JSTRY_DESTRUCTURING_ITERCLOSE: { + // Whether the destructuring iterator is done is at the top of the + // stack. The iterator object is second from the top. + MOZ_ASSERT(tn->stackDepth > 1); + Value* sp = regs.spForStackDepth(tn->stackDepth); + MOZ_ASSERT(sp[-1].isBoolean()); + if (sp[-1].isFalse()) { + RootedObject iterObject(cx, &sp[-2].toObject()); + if (!IteratorCloseForException(cx, iterObject)) { + SettleOnTryNote(cx, tn, ei, regs); + return ErrorReturnContinuation; + } + } + break; + } + case JSTRY_FOR_OF: case JSTRY_LOOP: break; -- cgit v1.2.3 From 1ea1ed151571a523d1c8016dcd314e12238cd785 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 12:27:00 +0100 Subject: Bug 1147371: Implement calling IteratorClose and "return" on iterators in yield* Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 80 +++++++++----- js/src/jit/BaselineCompiler.cpp | 14 ++- js/src/jit/BaselineCompiler.h | 3 + js/src/js.msg | 1 + .../tests/ecma_6/Generators/delegating-yield-2.js | 20 ++-- .../ecma_6/Generators/yield-star-iterator-close.js | 123 +++++++++++++++++++++ js/src/tests/ecma_6/shell.js | 11 +- js/src/vm/Interpreter.cpp | 12 +- js/src/vm/Interpreter.h | 1 + js/src/vm/Opcodes.h | 12 +- 10 files changed, 232 insertions(+), 45 deletions(-) create mode 100644 js/src/tests/ecma_6/Generators/yield-star-iterator-close.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index e2c906850..98679eac6 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4898,6 +4898,14 @@ BytecodeEmitter::emitIteratorClose(Maybe yieldStarTryStart, bool all return false; if (!ifReturnDone.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE RESULT return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE VALUE + return false; + if (!emitPrepareIteratorResult()) // ITER OLDRESULT FTYPE FVALUE VALUE RESULT + return false; + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RESULT VALUE + return false; + if (!emitFinishIteratorResult(true)) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT return false; if (!emit1(JSOP_SETRVAL)) // ITER OLDRESULT FTYPE FVALUE RESULT @@ -8059,61 +8067,77 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) // Catch location. stackDepth = uint32_t(depth); // ITER RESULT - if (!emit1(JSOP_POP)) // ITER - return false; - // THROW? = 'throw' in ITER - if (!emit1(JSOP_EXCEPTION)) // ITER EXCEPTION + if (!emit1(JSOP_EXCEPTION)) // ITER RESULT EXCEPTION return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER + if (!emitDupAt(2)) // ITER RESULT EXCEPTION ITER return false; - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER + if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER ITER return false; - if (!emitAtomOp(cx->names().throw_, JSOP_STRING)) // EXCEPTION ITER ITER "throw" + if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // ITER RESULT EXCEPTION ITER THROW return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER "throw" ITER + if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER THROW THROW return false; - if (!emit1(JSOP_IN)) // EXCEPTION ITER THROW? + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT EXCEPTION ITER THROW THROW UNDEFINED return false; - // if (THROW?) goto delegate - JumpList checkThrow; - if (!emitJump(JSOP_IFNE, &checkThrow)) // EXCEPTION ITER + if (!emit1(JSOP_EQ)) // ITER RESULT EXCEPTION ITER THROW ?EQL return false; - if (!emit1(JSOP_POP)) // EXCEPTION + + IfThenElseEmitter ifThrowMethodIsNotDefined(this); + if (!ifThrowMethodIsNotDefined.emitIf()) // ITER RESULT EXCEPTION ITER THROW return false; - if (!emit1(JSOP_THROW)) // throw EXCEPTION + if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) // throw return false; - - if (!emitJumpTargetAndPatch(checkThrow)) // delegate: + if (!ifThrowMethodIsNotDefined.emitEnd()) // ITER OLDRESULT EXCEPTION ITER THROW return false; - // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER - stackDepth = uint32_t(depth); - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.4. + // RESULT = ITER.throw(EXCEPTION) // ITER OLDRESULT EXCEPTION ITER THROW + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT EXCEPTION THROW ITER return false; - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER ITER + if (!emit2(JSOP_PICK, 2)) // ITER OLDRESULT THROW ITER EXCEPTION return false; - if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // EXCEPTION ITER ITER THROW + if (!emitCall(JSOP_CALL, 1, iter)) // ITER OLDRESULT RESULT return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER THROW ITER + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) // ITER OLDRESULT RESULT return false; - if (!emit2(JSOP_PICK, 3)) // ITER THROW ITER EXCEPTION + if (!emit1(JSOP_SWAP)) // ITER RESULT OLDRESULT return false; - if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT + if (!emit1(JSOP_POP)) // ITER RESULT return false; - checkTypeSet(JSOP_CALL); MOZ_ASSERT(this->stackDepth == depth); JumpList checkResult; + // Note that there is no GOSUB to the finally block here. If the iterator has a + // "throw" method, it does not perform IteratorClose per + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii. if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult return false; - // Catch epilogue. + // The finally block, IteratorClose logic. + + JumpTarget finallyStart{ 0 }; + if (!emitJumpTarget(&finallyStart)) + return false; + if (!emit1(JSOP_FINALLY)) // ITER RESULT FTYPE FVALUE + return false; + if (!emitDupAt(3)) // ITER RESULT FTYPE FVALUE ITER + return false; + if (!emitIteratorClose(Some(tryStart))) // ITER RESULT FTYPE FVALUE + return false; + if (!emit1(JSOP_RETSUB)) // ITER RESULT + return false; + + // Catch and finally epilogue. // This is a peace offering to ReconstructPCStack. See the note in EmitTry. if (!emit1(JSOP_NOP)) return false; - if (!tryNoteList.append(JSTRY_CATCH, depth, tryStart.offset + JSOP_TRY_LENGTH, tryEnd.offset)) + size_t tryStartOffset = tryStart.offset + JSOP_TRY_LENGTH; + if (!tryNoteList.append(JSTRY_CATCH, depth, tryStartOffset, tryEnd.offset)) + return false; + if (!tryNoteList.append(JSTRY_FINALLY, depth, tryStartOffset, finallyStart.offset)) return false; - // After the try/catch block: send the received value to the iterator. + // After the try-catch-finally block: send the received value to the iterator. if (!emitJumpTargetAndPatch(send)) // send: return false; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 2f0d41707..4524bae07 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -3975,7 +3975,7 @@ BaselineCompiler::emit_JSOP_MOREITER() } bool -BaselineCompiler::emit_JSOP_ISNOITER() +BaselineCompiler::emitIsMagicValue() { frame.syncStack(0); @@ -3993,6 +3993,12 @@ BaselineCompiler::emit_JSOP_ISNOITER() return true; } +bool +BaselineCompiler::emit_JSOP_ISNOITER() +{ + return emitIsMagicValue(); +} + bool BaselineCompiler::emit_JSOP_ENDITER() { @@ -4004,6 +4010,12 @@ BaselineCompiler::emit_JSOP_ENDITER() return emitOpIC(compiler.getStub(&stubSpace_)); } +bool +BaselineCompiler::emit_JSOP_ISGENCLOSING() +{ + return emitIsMagicValue(); +} + bool BaselineCompiler::emit_JSOP_GETRVAL() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 60dac0966..18e56bcd4 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -203,6 +203,7 @@ namespace jit { _(JSOP_MOREITER) \ _(JSOP_ISNOITER) \ _(JSOP_ENDITER) \ + _(JSOP_ISGENCLOSING) \ _(JSOP_GENERATOR) \ _(JSOP_INITIALYIELD) \ _(JSOP_YIELD) \ @@ -342,6 +343,8 @@ class BaselineCompiler : public BaselineCompilerSpecific MOZ_MUST_USE bool emitThrowConstAssignment(); MOZ_MUST_USE bool emitUninitializedLexicalCheck(const ValueOperand& val); + MOZ_MUST_USE bool emitIsMagicValue(); + MOZ_MUST_USE bool addPCMappingEntry(bool addIndexEntry); MOZ_MUST_USE bool addYieldOffset(); diff --git a/js/src/js.msg b/js/src/js.msg index 30c0d3035..8d492f523 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -583,3 +583,4 @@ MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "P // Iterator MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable") +MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method") diff --git a/js/src/tests/ecma_6/Generators/delegating-yield-2.js b/js/src/tests/ecma_6/Generators/delegating-yield-2.js index 918fb33e1..34cb3f4a9 100644 --- a/js/src/tests/ecma_6/Generators/delegating-yield-2.js +++ b/js/src/tests/ecma_6/Generators/delegating-yield-2.js @@ -25,8 +25,8 @@ assertThrowsValue(function () { outer.throw(42) }, 42); inner = g1(); outer = delegate(inner); assertIteratorNext(outer, 1); -inner.throw = function(e) { return e*2; }; -assertEq(84, outer.throw(42)); +inner.throw = function(e) { return { value: e*2 }; }; +assertEq(84, outer.throw(42).value); assertIteratorDone(outer, undefined); // Monkeypatching inner.next. @@ -41,7 +41,9 @@ outer = delegate(inner); assertIteratorNext(outer, 1); delete GeneratorObjectPrototype.throw; var outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42); -assertThrowsValue(outer_throw_42, 42); +// yield* protocol violation: no 'throw' method +assertThrowsInstanceOf(outer_throw_42, TypeError); +// Now done, so just throws. assertThrowsValue(outer_throw_42, 42); // Monkeypunch a different throw handler. @@ -49,11 +51,11 @@ inner = g2(); outer = delegate(inner); outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42); assertIteratorNext(outer, 1); -GeneratorObjectPrototype.throw = function(e) { return e*2; } -assertEq(84, outer_throw_42()); -assertEq(84, outer_throw_42()); +GeneratorObjectPrototype.throw = function(e) { return { value: e*2 }; } +assertEq(84, outer_throw_42().value); +assertEq(84, outer_throw_42().value); // This continues indefinitely. -assertEq(84, outer_throw_42()); +assertEq(84, outer_throw_42().value); assertIteratorDone(outer, undefined); // The same, but restoring the original pre-monkey throw. @@ -61,8 +63,8 @@ inner = g2(); outer = delegate(inner); outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42); assertIteratorNext(outer, 1); -assertEq(84, outer_throw_42()); -assertEq(84, outer_throw_42()); +assertEq(84, outer_throw_42().value); +assertEq(84, outer_throw_42().value); GeneratorObjectPrototype.throw = GeneratorObjectPrototype_throw; assertIteratorResult(outer_throw_42(), 42, false); assertIteratorDone(outer, undefined); diff --git a/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js b/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js new file mode 100644 index 000000000..ec62dd86d --- /dev/null +++ b/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js @@ -0,0 +1,123 @@ +// Tests that the "return" method on iterators is called in yield* +// expressions. + +function test() { + var returnCalled = 0; + var returnCalledExpected = 0; + var nextCalled = 0; + var nextCalledExpected = 0; + var iterable = {}; + iterable[Symbol.iterator] = makeIterator({ + next: function() { + nextCalled++; + return { done: false }; + }, + ret: function() { + returnCalled++; + return { done: true, value: "iter.return" }; + } + }); + + function* y() { + yield* iterable; + } + + // G.p.throw on an iterator without "throw" calls IteratorClose. + var g1 = y(); + g1.next(); + assertThrowsValue(function() { + g1.throw("foo"); + }, "foo"); + assertEq(returnCalled, ++returnCalledExpected); + assertEq(nextCalled, ++nextCalledExpected); + g1.next(); + assertEq(nextCalled, nextCalledExpected); + + // G.p.return calls "return", and if the result.done is true, return the + // result. + var g2 = y(); + g2.next(); + var v2 = g2.return("test return"); + assertEq(v2.done, true); + assertEq(v2.value, "iter.return"); + assertEq(returnCalled, ++returnCalledExpected); + assertEq(nextCalled, ++nextCalledExpected); + g2.next(); + assertEq(nextCalled, nextCalledExpected); + + // G.p.return calls "return", and if the result.done is false, continue + // yielding. + iterable[Symbol.iterator] = makeIterator({ + next: function() { + nextCalled++; + return { done: false }; + }, + ret: function() { + returnCalled++; + return { done: false, value: "iter.return" }; + } + }); + var g3 = y(); + g3.next(); + var v3 = g3.return("test return"); + assertEq(v3.done, false); + assertEq(v3.value, "iter.return"); + assertEq(returnCalled, ++returnCalledExpected); + assertEq(nextCalled, ++nextCalledExpected); + g3.next(); + assertEq(nextCalled, ++nextCalledExpected); + + // G.p.return throwing does not re-call iter.return. + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + throw "in iter.return"; + } + }); + var g4 = y(); + g4.next(); + assertThrowsValue(function() { + g4.return("in test"); + }, "in iter.return"); + assertEq(returnCalled, ++returnCalledExpected); + + // G.p.return expects iter.return to return an Object. + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + return 42; + } + }); + var g5 = y(); + g5.next(); + assertThrowsInstanceOf(function() { + g5.return("foo"); + }, TypeError); + assertEq(returnCalled, ++returnCalledExpected); + + // IteratorClose expects iter.return to return an Object. + var g6 = y(); + g6.next(); + assertThrowsInstanceOf(function() { + g6.throw("foo"); + }, TypeError); + assertEq(returnCalled, ++returnCalledExpected); + + // G.p.return passes its argument to "return". + iterable[Symbol.iterator] = makeIterator({ + ret: function(x) { + assertEq(x, "in test"); + returnCalled++; + return { done: true }; + } + }); + var g7 = y(); + g7.next(); + g7.return("in test"); + assertEq(returnCalled, ++returnCalledExpected); +} + +test(); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/shell.js b/js/src/tests/ecma_6/shell.js index 1d067a282..4da9221d6 100644 --- a/js/src/tests/ecma_6/shell.js +++ b/js/src/tests/ecma_6/shell.js @@ -22,14 +22,17 @@ /** Make an iterator with a return method. */ global.makeIterator = function makeIterator(overrides) { var iterator = { - next: function() { + throw: function(e) { + throw e; + }, + next: function(x) { if (overrides && overrides.next) - return overrides.next(); + return overrides.next(x); return { done: false }; }, - return: function() { + return: function(x) { if (overrides && overrides.ret) - return overrides.ret(); + return overrides.ret(x); return { done: true }; } }; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 923c824ce..9a8c6777f 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1887,7 +1887,6 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) -CASE(JSOP_UNUSED187) CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED209) CASE(JSOP_UNUSED210) @@ -2182,6 +2181,13 @@ CASE(JSOP_ENDITER) } END_CASE(JSOP_ENDITER) +CASE(JSOP_ISGENCLOSING) +{ + bool b = REGS.sp[-1].isMagic(JS_GENERATOR_CLOSING); + PUSH_BOOLEAN(b); +} +END_CASE(JSOP_ISGENCLOSING) + CASE(JSOP_DUP) { MOZ_ASSERT(REGS.stackDepth() >= 1); @@ -5074,6 +5080,10 @@ js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind) JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "return"); break; + case CheckIsObjectKind::IteratorThrow: + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "throw"); + break; case CheckIsObjectKind::GetIterator: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_GET_ITER_RETURNED_PRIMITIVE); break; diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 3a24240fc..38e23ec06 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -563,6 +563,7 @@ ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, const char* r enum class CheckIsObjectKind : uint8_t { IteratorNext, IteratorReturn, + IteratorThrow, GetIterator }; diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index b59b9388c..84f08c4d5 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1916,8 +1916,16 @@ * Stack: => this */ \ macro(JSOP_GLOBALTHIS, 186,"globalthis", NULL, 1, 0, 1, JOF_BYTE) \ - macro(JSOP_UNUSED187, 187,"unused187", NULL, 1, 0, 0, JOF_BYTE) \ - \ + /* + * Pushes a boolean indicating whether the top of the stack is + * MagicValue(JS_GENERATOR_CLOSING). + * + * Category: Statements + * Type: For-In Statement + * Operands: + * Stack: val => val, res + */ \ + macro(JSOP_ISGENCLOSING, 187, "isgenclosing", NULL, 1, 1, 2, JOF_BYTE) \ /* * Pushes unsigned 24-bit int immediate integer operand onto the stack. * Category: Literals -- cgit v1.2.3 From fdedd57c60d35bed3e6cde12084b7abe08153ed3 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 12:28:12 +0100 Subject: Bug 1147371: Implement JSOP_PICK and JSOP_UNPICK in the expression decompiler Issue #74 --- js/src/jsopcode.cpp | 28 ++++++++++++++++++++++++++++ js/src/jsopcode.h | 1 + js/src/jsopcodeinlines.h | 3 ++- 3 files changed, 31 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 31bbfb471..eadbca4f8 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -460,6 +460,34 @@ BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t* offsetStack, uint offsetStack[stackDepth] = tmp; } break; + + case JSOP_PICK: { + jsbytecode* pc = script_->offsetToPC(offset); + unsigned n = GET_UINT8(pc); + MOZ_ASSERT(ndefs == n + 1); + if (offsetStack) { + uint32_t top = stackDepth + n; + uint32_t tmp = offsetStack[stackDepth]; + for (uint32_t i = stackDepth; i < top; i++) + offsetStack[i] = offsetStack[i + 1]; + offsetStack[top] = tmp; + } + break; + } + + case JSOP_UNPICK: { + jsbytecode* pc = script_->offsetToPC(offset); + unsigned n = GET_UINT8(pc); + MOZ_ASSERT(ndefs == n + 1); + if (offsetStack) { + uint32_t top = stackDepth + n; + uint32_t tmp = offsetStack[top]; + for (uint32_t i = top; i > stackDepth; i--) + offsetStack[i] = offsetStack[i - 1]; + offsetStack[stackDepth] = tmp; + } + break; + } } stackDepth += ndefs; return stackDepth; diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 4f7859665..e56eebb2d 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -423,6 +423,7 @@ BytecodeFallsThrough(JSOp op) case JSOP_RETRVAL: case JSOP_FINALYIELDRVAL: case JSOP_THROW: + case JSOP_THROWMSG: case JSOP_TABLESWITCH: return false; case JSOP_GOSUB: diff --git a/js/src/jsopcodeinlines.h b/js/src/jsopcodeinlines.h index cf9c8357a..5b0ce0cf9 100644 --- a/js/src/jsopcodeinlines.h +++ b/js/src/jsopcodeinlines.h @@ -27,6 +27,7 @@ GetDefCount(JSScript* script, unsigned offset) case JSOP_AND: return 1; case JSOP_PICK: + case JSOP_UNPICK: /* * Pick pops and pushes how deep it looks in the stack + 1 * items. i.e. if the stack were |a b[2] c[1] d[0]|, pick 2 @@ -44,7 +45,7 @@ GetUseCount(JSScript* script, unsigned offset) { jsbytecode* pc = script->offsetToPC(offset); - if (JSOp(*pc) == JSOP_PICK) + if (JSOp(*pc) == JSOP_PICK || JSOp(*pc) == JSOP_UNPICK) return pc[1] + 1; if (CodeSpec[*pc].nuses == -1) return StackUses(script, pc); -- cgit v1.2.3 From 114eb8bf48ca0288f44705853239bdf198eeecdb Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 12:29:12 +0100 Subject: Bug 1147371: Always decompile argument names in self-hosted code in the caller frame Issue #74 --- js/src/jsopcode.cpp | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index eadbca4f8..6adb5401e 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -92,7 +92,8 @@ const char * const js::CodeName[] = { /************************************************************************/ -#define COUNTS_LEN 16 +static bool +DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res); size_t js::GetVariableBytecodeLength(jsbytecode* pc) @@ -1258,6 +1259,24 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc) return write(loadAtom(pc)); case JSOP_GETARG: { unsigned slot = GET_ARGNO(pc); + + // For self-hosted scripts that are called from non-self-hosted code, + // decompiling the parameter name in the self-hosted script is + // unhelpful. Decompile the argument name instead. + if (script->selfHosted()) { + char* result; + if (!DecompileArgumentFromStack(cx, slot, &result)) + return false; + + // Note that decompiling the argument in the parent frame might + // not succeed. + if (result) { + bool ok = write(result); + js_free(result); + return ok; + } + } + JSAtom* atom = getArg(slot); if (!atom) return false; @@ -1621,12 +1640,17 @@ DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res) MOZ_ASSERT(frameIter.script()->selfHosted()); /* - * Get the second-to-top frame, the caller of the builtin that called the - * intrinsic. + * Get the second-to-top frame, the non-self-hosted caller of the builtin + * that called the intrinsic. */ ++frameIter; - if (frameIter.done() || !frameIter.hasScript() || frameIter.compartment() != cx->compartment()) + if (frameIter.done() || + !frameIter.hasScript() || + frameIter.script()->selfHosted() || + frameIter.compartment() != cx->compartment()) + { return true; + } RootedScript script(cx, frameIter.script()); jsbytecode* current = frameIter.pc(); -- cgit v1.2.3 From aae3a117342e8959c074ea9f36cefac772f608d3 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 15:54:49 +0100 Subject: Bug 1343375: Update RegExp.prototype.replace and .match to call ToLength(lastIndex) for non-global RegExp and handle recompilations [Depends on] Bug 1317397: Implement RegExp.lastIndex changes from ES2017 --- js/src/builtin/RegExp.js | 54 +++------------- js/src/builtin/RegExpLocalReplaceOpt.h.js | 28 ++++++-- .../RegExp/match-local-tolength-recompilation.js | 75 ++++++++++++++++++++++ .../RegExp/replace-local-tolength-lastindex.js | 22 +++++++ .../RegExp/replace-local-tolength-recompilation.js | 75 ++++++++++++++++++++++ 5 files changed, 203 insertions(+), 51 deletions(-) create mode 100644 js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js create mode 100644 js/src/tests/ecma_6/RegExp/replace-local-tolength-lastindex.js create mode 100644 js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js (limited to 'js/src') diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js index 1ffea0105..f71ce4f75 100644 --- a/js/src/builtin/RegExp.js +++ b/js/src/builtin/RegExp.js @@ -122,8 +122,7 @@ function RegExpMatch(string) { } // Step 5. - var sticky = !!(flags & REGEXP_STICKY_FLAG); - return RegExpLocalMatchOpt(rx, S, sticky); + return RegExpBuiltinExec(rx, S, false); } // Stes 4-6 @@ -220,37 +219,6 @@ function RegExpGlobalMatchOpt(rx, S, fullUnicode) { } } -// ES 2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.6 step 5. -// Optimized path for @@match without global flag. -function RegExpLocalMatchOpt(rx, S, sticky) { - // Step 4. - var lastIndex = ToLength(rx.lastIndex); - - // Step 8. - if (!sticky) { - lastIndex = 0; - } else { - if (lastIndex > S.length) { - // Steps 12.a.i-ii, 12.c.i.1-2. - rx.lastIndex = 0; - return null; - } - } - - // Steps 3, 9-25, except 12.a.i-ii, 12.c.i.1-2, 15. - var result = RegExpMatcher(rx, S, lastIndex); - if (result === null) { - // Steps 12.a.i-ii, 12.c.i.1-2. - rx.lastIndex = 0; - } else { - // Step 15. - if (sticky) - rx.lastIndex = result.index + result[0].length; - } - - return result; -} - // Checks if following properties and getters are not modified, and accessing // them not observed by content script: // * flags @@ -318,9 +286,10 @@ function RegExpReplace(string, replaceValue) { if (functionalReplace) { var elemBase = GetElemBaseForLambda(replaceValue); - if (IsObject(elemBase)) + if (IsObject(elemBase)) { return RegExpGlobalReplaceOptElemBase(rx, S, lengthS, replaceValue, fullUnicode, elemBase); + } return RegExpGlobalReplaceOptFunc(rx, S, lengthS, replaceValue, fullUnicode); } @@ -336,18 +305,11 @@ function RegExpReplace(string, replaceValue) { fullUnicode); } - var sticky = !!(flags & REGEXP_STICKY_FLAG); - - if (functionalReplace) { - return RegExpLocalReplaceOptFunc(rx, S, lengthS, replaceValue, - sticky); - } - if (firstDollarIndex !== -1) { - return RegExpLocalReplaceOptSubst(rx, S, lengthS, replaceValue, - sticky, firstDollarIndex); - } - return RegExpLocalReplaceOpt(rx, S, lengthS, replaceValue, - sticky); + if (functionalReplace) + return RegExpLocalReplaceOptFunc(rx, S, lengthS, replaceValue); + if (firstDollarIndex !== -1) + return RegExpLocalReplaceOptSubst(rx, S, lengthS, replaceValue, firstDollarIndex); + return RegExpLocalReplaceOpt(rx, S, lengthS, replaceValue); } // Steps 8-16. diff --git a/js/src/builtin/RegExpLocalReplaceOpt.h.js b/js/src/builtin/RegExpLocalReplaceOpt.h.js index edc2e2056..96451d8a0 100644 --- a/js/src/builtin/RegExpLocalReplaceOpt.h.js +++ b/js/src/builtin/RegExpLocalReplaceOpt.h.js @@ -15,20 +15,33 @@ // steps 11.a-16. // Optimized path for @@replace with the following conditions: // * global flag is false -function FUNC_NAME(rx, S, lengthS, replaceValue, sticky +function FUNC_NAME(rx, S, lengthS, replaceValue #ifdef SUBSTITUTION , firstDollarIndex #endif ) { - var lastIndex; - if (sticky) { - lastIndex = ToLength(rx.lastIndex); + // 21.2.5.2.2 RegExpBuiltinExec, step 4. + var lastIndex = ToLength(rx.lastIndex); + + // 21.2.5.2.2 RegExpBuiltinExec, step 5. + // Side-effects in step 4 can recompile the RegExp, so we need to read the + // flags again and handle the case when global was enabled even though this + // function is optimized for non-global RegExps. + var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); + + // 21.2.5.2.2 RegExpBuiltinExec, steps 6-7. + var globalOrSticky = !!(flags & (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG)); + + if (globalOrSticky) { + // 21.2.5.2.2 RegExpBuiltinExec, step 12.a. if (lastIndex > lengthS) { + // FIXME: Implement changes for bug 1317397. rx.lastIndex = 0; return S; } } else { + // 21.2.5.2.2 RegExpBuiltinExec, step 8. lastIndex = 0; } @@ -37,7 +50,11 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, sticky // Step 11.b. if (result === null) { + // 21.2.5.2.2 RegExpBuiltinExec, steps 12.a.i, 12.c.i. + // FIXME: Implement changes for bug 1317397. rx.lastIndex = 0; + + // Steps 12-16. return S; } @@ -61,7 +78,8 @@ function FUNC_NAME(rx, S, lengthS, replaceValue, sticky // To set rx.lastIndex before RegExpGetComplexReplacement. var nextSourcePosition = position + matchLength; - if (sticky) + // 21.2.5.2.2 RegExpBuiltinExec, step 15. + if (globalOrSticky) rx.lastIndex = nextSourcePosition; var replacement; diff --git a/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js b/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js new file mode 100644 index 000000000..6cb286b8c --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js @@ -0,0 +1,75 @@ +// Side-effects when calling ToLength(regExp.lastIndex) in +// RegExp.prototype[@@match] for non-global RegExp can recompile the RegExp. + +for (var flag of ["", "y"]) { + var regExp = new RegExp("a", flag); + + regExp.lastIndex = { + valueOf() { + regExp.compile("b"); + return 0; + } + }; + + var result = regExp[Symbol.match]("b"); + assertEq(result !== null, true); +} + +// Recompilation modifies flag: +// Case 1: Adds global flag, validate by checking lastIndex. +var regExp = new RegExp("a", ""); +regExp.lastIndex = { + valueOf() { + // |regExp| is now in global mode, RegExpBuiltinExec should update the + // lastIndex property to reflect last match. + regExp.compile("a", "g"); + return 0; + } +}; +regExp[Symbol.match]("a"); +assertEq(regExp.lastIndex, 1); + +// Case 2: Removes sticky flag with match, validate by checking lastIndex. +var regExp = new RegExp("a", "y"); +regExp.lastIndex = { + valueOf() { + // |regExp| is no longer sticky, RegExpBuiltinExec shouldn't modify the + // lastIndex property. + regExp.compile("a", ""); + regExp.lastIndex = 9000; + return 0; + } +}; +regExp[Symbol.match]("a"); +assertEq(regExp.lastIndex, 9000); + +// Case 3.a: Removes sticky flag without match, validate by checking lastIndex. +var regExp = new RegExp("a", "y"); +regExp.lastIndex = { + valueOf() { + // |regExp| is no longer sticky, RegExpBuiltinExec shouldn't modify the + // lastIndex property. + regExp.compile("b", ""); + regExp.lastIndex = 9001; + return 0; + } +}; +regExp[Symbol.match]("a"); +assertEq(regExp.lastIndex, 0, "Update the expected value to |9001| after fixing 1317397"); + +// Case 3.b: Removes sticky flag without match, validate by checking lastIndex. +var regExp = new RegExp("a", "y"); +regExp.lastIndex = { + valueOf() { + // |regExp| is no longer sticky, RegExpBuiltinExec shouldn't modify the + // lastIndex property. + regExp.compile("b", ""); + regExp.lastIndex = 9002; + return 10000; + } +}; +regExp[Symbol.match]("a"); +assertEq(regExp.lastIndex, 0, "Update the expected value to |9002| after fixing 1317397"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/RegExp/replace-local-tolength-lastindex.js b/js/src/tests/ecma_6/RegExp/replace-local-tolength-lastindex.js new file mode 100644 index 000000000..7ba840e00 --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/replace-local-tolength-lastindex.js @@ -0,0 +1,22 @@ +// RegExp.prototype[@@replace] always executes ToLength(regExp.lastIndex) for +// non-global RegExps. + +for (var flag of ["", "g", "y", "gy"]) { + var regExp = new RegExp("a", flag); + + var called = false; + regExp.lastIndex = { + valueOf() { + assertEq(called, false); + called = true; + return 0; + } + }; + + assertEq(called, false); + regExp[Symbol.replace](""); + assertEq(called, !flag.includes("g")); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js b/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js new file mode 100644 index 000000000..30002c3ed --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js @@ -0,0 +1,75 @@ +// Side-effects when calling ToLength(regExp.lastIndex) in +// RegExp.prototype[@@replace] for non-global RegExp can recompile the RegExp. + +for (var flag of ["", "y"]) { + var regExp = new RegExp("a", flag); + + regExp.lastIndex = { + valueOf() { + regExp.compile("b"); + return 0; + } + }; + + var result = regExp[Symbol.replace]("b", "pass"); + assertEq(result, "pass"); +} + +// Recompilation modifies flag: +// Case 1: Adds global flag, validate by checking lastIndex. +var regExp = new RegExp("a", ""); +regExp.lastIndex = { + valueOf() { + // |regExp| is now in global mode, RegExpBuiltinExec should update the + // lastIndex property to reflect last match. + regExp.compile("a", "g"); + return 0; + } +}; +regExp[Symbol.replace]("a", ""); +assertEq(regExp.lastIndex, 1); + +// Case 2: Removes sticky flag with match, validate by checking lastIndex. +var regExp = new RegExp("a", "y"); +regExp.lastIndex = { + valueOf() { + // |regExp| is no longer sticky, RegExpBuiltinExec shouldn't modify the + // lastIndex property. + regExp.compile("a", ""); + regExp.lastIndex = 9000; + return 0; + } +}; +regExp[Symbol.replace]("a", ""); +assertEq(regExp.lastIndex, 9000); + +// Case 3.a: Removes sticky flag without match, validate by checking lastIndex. +var regExp = new RegExp("a", "y"); +regExp.lastIndex = { + valueOf() { + // |regExp| is no longer sticky, RegExpBuiltinExec shouldn't modify the + // lastIndex property. + regExp.compile("b", ""); + regExp.lastIndex = 9001; + return 0; + } +}; +regExp[Symbol.replace]("a", ""); +assertEq(regExp.lastIndex, 0, "Update the expected value to |9001| after fixing 1317397"); + +// Case 3.b: Removes sticky flag without match, validate by checking lastIndex. +var regExp = new RegExp("a", "y"); +regExp.lastIndex = { + valueOf() { + // |regExp| is no longer sticky, RegExpBuiltinExec shouldn't modify the + // lastIndex property. + regExp.compile("b", ""); + regExp.lastIndex = 9002; + return 10000; + } +}; +regExp[Symbol.replace]("a", ""); +assertEq(regExp.lastIndex, 0, "Update the expected value to |9002| after fixing 1317397"); + +if (typeof reportCompare === "function") + reportCompare(true, true); -- cgit v1.2.3 From 5fd5b2ac2f396eb1d8707a691aa26ad159ad25e3 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 16:01:06 +0100 Subject: Bug 1317397: Only set lastIndex for global or sticky RegExps in RegExpBuiltinExec per ES2017 --- js/src/builtin/RegExp.js | 80 ++++++++++---- js/src/builtin/RegExpLocalReplaceOpt.h.js | 12 +- js/src/builtin/Utilities.js | 12 +- .../jit-test/tests/basic/regexpLastIndexReset.js | 4 +- js/src/tests/ecma_2017/lastIndex-exec.js | 80 ++++++++++++++ .../tests/ecma_2017/lastIndex-match-or-replace.js | 122 +++++++++++++++++++++ js/src/tests/ecma_2017/lastIndex-search.js | 118 ++++++++++++++++++++ js/src/tests/ecma_3/String/15.5.4.11.js | 2 +- js/src/tests/ecma_5/RegExp/exec.js | 2 +- js/src/tests/ecma_6/RegExp/compile-lastIndex.js | 34 +++--- .../RegExp/match-local-tolength-recompilation.js | 4 +- .../RegExp/replace-local-tolength-recompilation.js | 4 +- js/src/tests/ecma_6/RegExp/search-trace.js | 2 + 13 files changed, 419 insertions(+), 57 deletions(-) create mode 100644 js/src/tests/ecma_2017/lastIndex-exec.js create mode 100644 js/src/tests/ecma_2017/lastIndex-match-or-replace.js create mode 100644 js/src/tests/ecma_2017/lastIndex-search.js (limited to 'js/src') diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js index f71ce4f75..0b849292c 100644 --- a/js/src/builtin/RegExp.js +++ b/js/src/builtin/RegExp.js @@ -609,7 +609,8 @@ function RegExpGlobalReplaceShortOpt(rx, S, lengthS, replaceValue, fullUnicode) #undef SUBSTITUTION #undef FUNC_NAME -// ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.9. +// ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 +// 21.2.5.9 RegExp.prototype [ @@search ] ( string ) function RegExpSearch(string) { // Step 1. var rx = this; @@ -621,41 +622,69 @@ function RegExpSearch(string) { // Step 3. var S = ToString(string); + // Step 4. + var previousLastIndex = rx.lastIndex; + + // Step 5. + var lastIndexIsZero = SameValue(previousLastIndex, 0); + if (!lastIndexIsZero) + rx.lastIndex = 0; + if (IsRegExpMethodOptimizable(rx) && S.length < 0x7fff) { // Step 6. var result = RegExpSearcher(rx, S, 0); - // Step 8. + // We need to consider two cases: + // + // 1. Neither global nor sticky is set: + // RegExpBuiltinExec doesn't modify lastIndex for local RegExps, that + // means |SameValue(rx.lastIndex, 0)| is true after calling exec. The + // comparison in steps 7-8 |SameValue(rx.lastIndex, previousLastIndex)| + // is therefore equal to the already computed |lastIndexIsZero| value. + // + // 2. Global or sticky flag is set. + // RegExpBuiltinExec will always update lastIndex and we need to + // restore the property to its original value. + + // Steps 7-8. + if (!lastIndexIsZero) { + rx.lastIndex = previousLastIndex; + } else { + var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); + if (flags & (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG)) + rx.lastIndex = previousLastIndex; + } + + // Step 9. if (result === -1) return -1; - // Step 9. + // Step 10. return result & 0x7fff; } - return RegExpSearchSlowPath(rx, S); + return RegExpSearchSlowPath(rx, S, previousLastIndex); } -// ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.9 -// steps 4-9. -function RegExpSearchSlowPath(rx, S) { - // Step 4. - var previousLastIndex = rx.lastIndex; - - // Step 5. - rx.lastIndex = 0; - +// ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 +// 21.2.5.9 RegExp.prototype [ @@search ] ( string ) +// Steps 6-10. +function RegExpSearchSlowPath(rx, S, previousLastIndex) { // Step 6. var result = RegExpExec(rx, S, false); // Step 7. - rx.lastIndex = previousLastIndex; + var currentLastIndex = rx.lastIndex; // Step 8. + if (!SameValue(currentLastIndex, previousLastIndex)) + rx.lastIndex = previousLastIndex; + + // Step 9. if (result === null) return -1; - // Step 9. + // Step 10. return result.index; } @@ -904,15 +933,16 @@ function RegExpExec(R, S, forTest) { return forTest ? result !== null : result; } -// ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2. +// ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 +// 21.2.5.2.2 Runtime Semantics: RegExpBuiltinExec ( R, S ) function RegExpBuiltinExec(R, S, forTest) { - // ES6 21.2.5.2.1 step 6. + // 21.2.5.2.1 Runtime Semantics: RegExpExec, step 5. // This check is here for RegExpTest. RegExp_prototype_Exec does same // thing already. if (!IsRegExpObject(R)) return UnwrapAndCallRegExpBuiltinExec(R, S, forTest); - // Steps 1-2 (skipped). + // Steps 1-3 (skipped). // Step 4. var lastIndex = ToLength(R.lastIndex); @@ -927,9 +957,11 @@ function RegExpBuiltinExec(R, S, forTest) { if (!globalOrSticky) { lastIndex = 0; } else { + // Step 12.a. if (lastIndex > S.length) { - // Steps 12.a.i-ii, 12.c.i.1-2. - R.lastIndex = 0; + // Steps 12.a.i-ii. + if (globalOrSticky) + R.lastIndex = 0; return forTest ? false : null; } } @@ -939,7 +971,8 @@ function RegExpBuiltinExec(R, S, forTest) { var endIndex = RegExpTester(R, S, lastIndex); if (endIndex == -1) { // Steps 12.a.i-ii, 12.c.i.1-2. - R.lastIndex = 0; + if (globalOrSticky) + R.lastIndex = 0; return false; } @@ -953,8 +986,9 @@ function RegExpBuiltinExec(R, S, forTest) { // Steps 3, 9-25, except 12.a.i-ii, 12.c.i.1-2, 15. var result = RegExpMatcher(R, S, lastIndex); if (result === null) { - // Steps 12.a.i-ii, 12.c.i.1-2. - R.lastIndex = 0; + // Steps 12.a.i, 12.c.i. + if (globalOrSticky) + R.lastIndex = 0; } else { // Step 15. if (globalOrSticky) diff --git a/js/src/builtin/RegExpLocalReplaceOpt.h.js b/js/src/builtin/RegExpLocalReplaceOpt.h.js index 96451d8a0..1acd6a73a 100644 --- a/js/src/builtin/RegExpLocalReplaceOpt.h.js +++ b/js/src/builtin/RegExpLocalReplaceOpt.h.js @@ -11,7 +11,7 @@ // * FUNCTIONAL -- replaceValue is a function // * neither of above -- replaceValue is a string without "$" -// ES 2017 draft 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8 +// ES 2017 draft 6390c2f1b34b309895d31d8c0512eac8660a0210 21.2.5.8 // steps 11.a-16. // Optimized path for @@replace with the following conditions: // * global flag is false @@ -36,8 +36,10 @@ function FUNC_NAME(rx, S, lengthS, replaceValue if (globalOrSticky) { // 21.2.5.2.2 RegExpBuiltinExec, step 12.a. if (lastIndex > lengthS) { - // FIXME: Implement changes for bug 1317397. - rx.lastIndex = 0; + if (globalOrSticky) + rx.lastIndex = 0; + + // Steps 12-16. return S; } } else { @@ -51,8 +53,8 @@ function FUNC_NAME(rx, S, lengthS, replaceValue // Step 11.b. if (result === null) { // 21.2.5.2.2 RegExpBuiltinExec, steps 12.a.i, 12.c.i. - // FIXME: Implement changes for bug 1317397. - rx.lastIndex = 0; + if (globalOrSticky) + rx.lastIndex = 0; // Steps 12-16. return S; diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 2dece3801..3145b4d51 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -106,7 +106,17 @@ function ToLength(v) { return std_Math_min(v, 0x1fffffffffffff); } -/* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */ +// ES2017 draft rev aebf014403a3e641fb1622aec47c40f051943527 +// 7.2.9 SameValue ( x, y ) +function SameValue(x, y) { + if (x === y) { + return (x !== 0) || (1 / x === 1 / y); + } + return (x !== x && y !== y); +} + +// ES2017 draft rev aebf014403a3e641fb1622aec47c40f051943527 +// 7.2.10 SameValueZero ( x, y ) function SameValueZero(x, y) { return x === y || (x !== x && y !== y); } diff --git a/js/src/jit-test/tests/basic/regexpLastIndexReset.js b/js/src/jit-test/tests/basic/regexpLastIndexReset.js index 2a54d8ef5..dbe3c3b76 100644 --- a/js/src/jit-test/tests/basic/regexpLastIndexReset.js +++ b/js/src/jit-test/tests/basic/regexpLastIndexReset.js @@ -7,7 +7,7 @@ function test() { pattern.lastIndex = 3; var result = pattern.exec(string); assertEq(result, null); - assertEq(pattern.lastIndex, 0); + assertEq(pattern.lastIndex, 3); } for (let i = 0; i < 10; i++) { @@ -18,7 +18,7 @@ function test2() { pattern.lastIndex = 3; var result = pattern.test(string); assertEq(result, false); - assertEq(pattern.lastIndex, 0); + assertEq(pattern.lastIndex, 3); } for (let i = 0; i < 10; i++) { diff --git a/js/src/tests/ecma_2017/lastIndex-exec.js b/js/src/tests/ecma_2017/lastIndex-exec.js new file mode 100644 index 000000000..f42facbe3 --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-exec.js @@ -0,0 +1,80 @@ +// RegExp.prototype.exec: Test lastIndex changes for ES2017. + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCases = [ + { regExp: /a/, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: 0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -0, input: "a", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: -0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -0, input: "b", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -1, input: "a", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: -1, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -1, input: "b", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: 100, input: "a", result: 100 }, + { regExp: /a/g, lastIndex: 100, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 100, input: "a", result: 0 }, +]; + +// Basic test. +for (let {regExp, lastIndex, input, result} of testCases) { + let re = new RegExp(regExp); + re.lastIndex = lastIndex; + re.exec(input); + assertEq(re.lastIndex, result); +} + +// Test when lastIndex is non-writable. +for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re.exec(input), TypeError); + } else { + re.exec(input); + } + assertEq(re.lastIndex, lastIndex); +} + +// Test when lastIndex is changed to non-writable as a side-effect. +for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + let called = false; + re.lastIndex = { + valueOf() { + assertEq(called, false); + called = true; + Object.defineProperty(re, "lastIndex", { value: 9000, writable: false }); + return lastIndex; + } + }; + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re.exec(input), TypeError); + } else { + re.exec(input); + } + assertEq(re.lastIndex, 9000); + assertEq(called, true); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_2017/lastIndex-match-or-replace.js b/js/src/tests/ecma_2017/lastIndex-match-or-replace.js new file mode 100644 index 000000000..b0a8b537c --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-match-or-replace.js @@ -0,0 +1,122 @@ +// RegExp.prototype[Symbol.match, Symbol.replace]: Test lastIndex changes for ES2017. + +// RegExp-like class to test the RegExp method slow paths. +class DuckRegExp extends RegExp { + constructor(pattern, flags) { + return Object.create(DuckRegExp.prototype, { + regExp: { + value: new RegExp(pattern, flags) + }, + lastIndex: { + value: 0, writable: true, enumerable: false, configurable: false + } + }); + } + + exec(...args) { + this.regExp.lastIndex = this.lastIndex; + try { + return this.regExp.exec(...args); + } finally { + if (this.global || this.sticky) + this.lastIndex = this.regExp.lastIndex; + } + } + + get source() { return this.regExp.source; } + + get global() { return this.regExp.global; } + get ignoreCase() { return this.regExp.ignoreCase; } + get multiline() { return this.regExp.multiline; } + get sticky() { return this.regExp.sticky; } + get unicode() { return this.regExp.unicode; } +} + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCases = [ + { regExp: /a/, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -0, input: "a", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -0, input: "b", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -1, input: "a", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -1, input: "b", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: 100, input: "a", result: 100 }, + { regExp: /a/g, lastIndex: 100, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 100, input: "a", result: 0 }, +]; + +for (let method of [RegExp.prototype[Symbol.match], RegExp.prototype[Symbol.replace]]) { + for (let Constructor of [RegExp, DuckRegExp]) { + // Basic test. + for (let {regExp, lastIndex, input, result} of testCases) { + let re = new Constructor(regExp); + re.lastIndex = lastIndex; + Reflect.apply(method, re, [input]); + assertEq(re.lastIndex, result); + } + + // Test when lastIndex is non-writable. + for (let {regExp, lastIndex, input} of testCases) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError); + } else { + Reflect.apply(method, re, [input]); + } + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is changed to non-writable as a side-effect. + for (let {regExp, lastIndex, input, result} of testCases) { + let re = new Constructor(regExp); + let called = false; + re.lastIndex = { + valueOf() { + assertEq(called, false); + called = true; + Object.defineProperty(re, "lastIndex", { value: 9000, writable: false }); + return lastIndex; + } + }; + if (re.sticky) { + assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError); + assertEq(called, true); + assertEq(re.lastIndex, 9000); + } else if (re.global) { + Reflect.apply(method, re, [input]); + assertEq(called, false); + assertEq(re.lastIndex, result); + } else { + Reflect.apply(method, re, [input]); + assertEq(called, true); + assertEq(re.lastIndex, 9000); + } + } + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_2017/lastIndex-search.js b/js/src/tests/ecma_2017/lastIndex-search.js new file mode 100644 index 000000000..5953b3a88 --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-search.js @@ -0,0 +1,118 @@ +// RegExp.prototype[Symbol.search]: Test lastIndex changes for ES2017. + +// RegExp-like class to test the RegExp method slow paths. +class DuckRegExp extends RegExp { + constructor(pattern, flags) { + return Object.create(DuckRegExp.prototype, { + regExp: { + value: new RegExp(pattern, flags) + }, + lastIndex: { + value: 0, writable: true, enumerable: false, configurable: false + } + }); + } + + exec(...args) { + this.regExp.lastIndex = this.lastIndex; + try { + return this.regExp.exec(...args); + } finally { + if (this.global || this.sticky) + this.lastIndex = this.regExp.lastIndex; + } + } + + get source() { return this.regExp.source; } + + get global() { return this.regExp.global; } + get ignoreCase() { return this.regExp.ignoreCase; } + get multiline() { return this.regExp.multiline; } + get sticky() { return this.regExp.sticky; } + get unicode() { return this.regExp.unicode; } +} + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCasesNotPositiveZero = [ + { regExp: /a/, lastIndex: -1, input: "a" }, + { regExp: /a/g, lastIndex: -1, input: "a" }, + { regExp: /a/y, lastIndex: -1, input: "a" }, + + { regExp: /a/, lastIndex: 100, input: "a" }, + { regExp: /a/g, lastIndex: 100, input: "a" }, + { regExp: /a/y, lastIndex: 100, input: "a" }, + + { regExp: /a/, lastIndex: -1, input: "b" }, + { regExp: /a/g, lastIndex: -1, input: "b" }, + { regExp: /a/y, lastIndex: -1, input: "b" }, + + { regExp: /a/, lastIndex: -0, input: "a" }, + { regExp: /a/g, lastIndex: -0, input: "a" }, + { regExp: /a/y, lastIndex: -0, input: "a" }, + + { regExp: /a/, lastIndex: -0, input: "b" }, + { regExp: /a/g, lastIndex: -0, input: "b" }, + { regExp: /a/y, lastIndex: -0, input: "b" }, +]; + +const testCasesPositiveZero = [ + { regExp: /a/, lastIndex: 0, input: "a" }, + { regExp: /a/g, lastIndex: 0, input: "a" }, + { regExp: /a/y, lastIndex: 0, input: "a" }, + + { regExp: /a/, lastIndex: 0, input: "b" }, + { regExp: /a/g, lastIndex: 0, input: "b" }, + { regExp: /a/y, lastIndex: 0, input: "b" }, +]; + +const testCases = [...testCasesNotPositiveZero, ...testCasesPositiveZero]; + +for (let Constructor of [RegExp, DuckRegExp]) { + // Basic test. + for (let {regExp, lastIndex, input} of testCases) { + let re = new Constructor(regExp); + re.lastIndex = lastIndex; + re[Symbol.search](input); + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is non-writable and not positive zero. + for (let {regExp, lastIndex, input} of testCasesNotPositiveZero) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + assertThrowsInstanceOf(() => re[Symbol.search](input), TypeError); + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is non-writable and positive zero. + for (let {regExp, lastIndex, input} of testCasesPositiveZero) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re[Symbol.search](input), TypeError); + } else { + re[Symbol.search](input); + } + assertEq(re.lastIndex, lastIndex); + } + + // Test lastIndex isn't converted to a number. + for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + let badIndex = { + valueOf() { + assertEq(false, true); + } + }; + re.lastIndex = badIndex; + re[Symbol.search](input); + assertEq(re.lastIndex, badIndex); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_3/String/15.5.4.11.js b/js/src/tests/ecma_3/String/15.5.4.11.js index 0fd6caaf4..a5515286a 100644 --- a/js/src/tests/ecma_3/String/15.5.4.11.js +++ b/js/src/tests/ecma_3/String/15.5.4.11.js @@ -157,7 +157,7 @@ reportCompare( rex = /y/, rex.lastIndex = 1; reportCompare( - "xxx0", + "xxx1", "xxx".replace(rex, "y") + rex.lastIndex, "Section 25" ); diff --git a/js/src/tests/ecma_5/RegExp/exec.js b/js/src/tests/ecma_5/RegExp/exec.js index 411f348d9..4284b6e01 100644 --- a/js/src/tests/ecma_5/RegExp/exec.js +++ b/js/src/tests/ecma_5/RegExp/exec.js @@ -165,7 +165,7 @@ r = /abc/; r.lastIndex = -17; res = r.exec("cdefg"); assertEq(res, null); -assertEq(r.lastIndex, 0); +assertEq(r.lastIndex, -17); r = /abc/g; r.lastIndex = -42; diff --git a/js/src/tests/ecma_6/RegExp/compile-lastIndex.js b/js/src/tests/ecma_6/RegExp/compile-lastIndex.js index 80c820f43..5bd7e0b98 100644 --- a/js/src/tests/ecma_6/RegExp/compile-lastIndex.js +++ b/js/src/tests/ecma_6/RegExp/compile-lastIndex.js @@ -17,15 +17,12 @@ print(BUGNUMBER + ": " + summary); var regex = /foo/i; -// Aside from making .lastIndex non-writable, this has two incidental effects +// Aside from making .lastIndex non-writable, this has one incidental effect // ubiquitously tested through the remainder of this test: // // * RegExp.prototype.compile will do everything it ordinarily does, BUT it // will throw a TypeError when attempting to zero .lastIndex immediately // before succeeding overall. -// * RegExp.prototype.test for a non-global and non-sticky regular expression, -// in case of a match, will return true (as normal). BUT if no match is -// found, it will throw a TypeError when attempting to modify .lastIndex. // // Ain't it great? Object.defineProperty(regex, "lastIndex", { value: 42, writable: false }); @@ -40,8 +37,8 @@ assertEq(regex.lastIndex, 42); assertEq(regex.test("foo"), true); assertEq(regex.test("FOO"), true); -assertThrowsInstanceOf(() => regex.test("bar"), TypeError); -assertThrowsInstanceOf(() => regex.test("BAR"), TypeError); +assertEq(regex.test("bar"), false); +assertEq(regex.test("BAR"), false); assertThrowsInstanceOf(() => regex.compile("bar"), TypeError); @@ -52,10 +49,10 @@ assertEq(regex.unicode, false); assertEq(regex.sticky, false); assertEq(Object.getOwnPropertyDescriptor(regex, "lastIndex").writable, false); assertEq(regex.lastIndex, 42); -assertThrowsInstanceOf(() => regex.test("foo"), TypeError); -assertThrowsInstanceOf(() => regex.test("FOO"), TypeError); +assertEq(regex.test("foo"), false); +assertEq(regex.test("FOO"), false); assertEq(regex.test("bar"), true); -assertThrowsInstanceOf(() => regex.test("BAR"), TypeError); +assertEq(regex.test("BAR"), false); assertThrowsInstanceOf(() => regex.compile("^baz", "m"), TypeError); @@ -66,19 +63,16 @@ assertEq(regex.unicode, false); assertEq(regex.sticky, false); assertEq(Object.getOwnPropertyDescriptor(regex, "lastIndex").writable, false); assertEq(regex.lastIndex, 42); -assertThrowsInstanceOf(() => regex.test("foo"), TypeError); -assertThrowsInstanceOf(() => regex.test("FOO"), TypeError); -assertThrowsInstanceOf(() => regex.test("bar"), TypeError); -assertThrowsInstanceOf(() => regex.test("BAR"), TypeError); +assertEq(regex.test("foo"), false); +assertEq(regex.test("FOO"), false); +assertEq(regex.test("bar"), false); +assertEq(regex.test("BAR"), false); assertEq(regex.test("baz"), true); -assertThrowsInstanceOf(() => regex.test("BAZ"), TypeError); -assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901baz"), - TypeError); +assertEq(regex.test("BAZ"), false); +assertEq(regex.test("012345678901234567890123456789012345678901baz"), false); assertEq(regex.test("012345678901234567890123456789012345678901\nbaz"), true); -assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901BAZ"), - TypeError); -assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901\nBAZ"), - TypeError); +assertEq(regex.test("012345678901234567890123456789012345678901BAZ"), false); +assertEq(regex.test("012345678901234567890123456789012345678901\nBAZ"), false); /******************************************************************************/ diff --git a/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js b/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js index 6cb286b8c..9a992f81f 100644 --- a/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js +++ b/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js @@ -55,7 +55,7 @@ regExp.lastIndex = { } }; regExp[Symbol.match]("a"); -assertEq(regExp.lastIndex, 0, "Update the expected value to |9001| after fixing 1317397"); +assertEq(regExp.lastIndex, 9001); // Case 3.b: Removes sticky flag without match, validate by checking lastIndex. var regExp = new RegExp("a", "y"); @@ -69,7 +69,7 @@ regExp.lastIndex = { } }; regExp[Symbol.match]("a"); -assertEq(regExp.lastIndex, 0, "Update the expected value to |9002| after fixing 1317397"); +assertEq(regExp.lastIndex, 9002); if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js b/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js index 30002c3ed..e03177286 100644 --- a/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js +++ b/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js @@ -55,7 +55,7 @@ regExp.lastIndex = { } }; regExp[Symbol.replace]("a", ""); -assertEq(regExp.lastIndex, 0, "Update the expected value to |9001| after fixing 1317397"); +assertEq(regExp.lastIndex, 9001); // Case 3.b: Removes sticky flag without match, validate by checking lastIndex. var regExp = new RegExp("a", "y"); @@ -69,7 +69,7 @@ regExp.lastIndex = { } }; regExp[Symbol.replace]("a", ""); -assertEq(regExp.lastIndex, 0, "Update the expected value to |9002| after fixing 1317397"); +assertEq(regExp.lastIndex, 9002); if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/js/src/tests/ecma_6/RegExp/search-trace.js b/js/src/tests/ecma_6/RegExp/search-trace.js index ef14514c6..fc6bee754 100644 --- a/js/src/tests/ecma_6/RegExp/search-trace.js +++ b/js/src/tests/ecma_6/RegExp/search-trace.js @@ -56,6 +56,7 @@ assertEq(log, "get:lastIndex," + "set:lastIndex," + "get:exec,call:exec," + + "get:lastIndex," + "set:lastIndex," + "get:result[index],"); @@ -70,6 +71,7 @@ assertEq(log, "get:lastIndex," + "set:lastIndex," + "get:exec,call:exec," + + "get:lastIndex," + "set:lastIndex,"); if (typeof reportCompare === "function") -- cgit v1.2.3 From 8bb9649135c384a08b78295b9d07be32d50967d1 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 12:37:06 +0200 Subject: Bug 1331444 - Keep iterators alive in Ion in for-of loops for IteratorClose due to exceptions Issue #74 --- js/src/jit-test/tests/for-of/bug-1331444.js | 7 +++++++ js/src/jit/IonBuilder.cpp | 19 ++++++++++++++----- js/src/jit/IonBuilder.h | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 js/src/jit-test/tests/for-of/bug-1331444.js (limited to 'js/src') diff --git a/js/src/jit-test/tests/for-of/bug-1331444.js b/js/src/jit-test/tests/for-of/bug-1331444.js new file mode 100644 index 000000000..9770c584b --- /dev/null +++ b/js/src/jit-test/tests/for-of/bug-1331444.js @@ -0,0 +1,7 @@ +// |jit-test| error: ReferenceError + +symbols = [Symbol]; +for (comparator of[, ]) + for (a of symbols) + for (;;) + expect; diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index c4df415a4..4318db2b6 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -961,11 +961,16 @@ IonBuilder::processIterators() // Find phis that must directly hold an iterator live. Vector worklist; for (size_t i = 0; i < iterators_.length(); i++) { - MInstruction* ins = iterators_[i]; - for (MUseDefIterator iter(ins); iter; iter++) { - if (iter.def()->isPhi()) { - if (!worklist.append(iter.def()->toPhi())) - return false; + MDefinition* def = iterators_[i]; + if (def->isPhi()) { + if (!worklist.append(def->toPhi())) + return false; + } else { + for (MUseDefIterator iter(def); iter; iter++) { + if (iter.def()->isPhi()) { + if (!worklist.append(iter.def()->toPhi())) + return false; + } } } } @@ -1936,6 +1941,10 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CALLITER: case JSOP_NEW: case JSOP_SUPERCALL: + if (op == JSOP_CALLITER) { + if (!outermostBuilder()->iterators_.append(current->peek(-1))) + return false; + } return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL); case JSOP_EVAL: diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 0d1bdb1e3..c3cd9700a 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -1242,7 +1242,7 @@ class IonBuilder Vector loops_; Vector switches_; Vector labels_; - Vector iterators_; + Vector iterators_; Vector loopHeaders_; BaselineInspector* inspector; -- cgit v1.2.3 From 3df7c50fcccff433e4e24a0f1ce26859977948b4 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 12:37:44 +0200 Subject: Bug 1332155 - Skip non-try-related trynotes when asserting jump targets Issue #74 --- js/src/jsscript.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 929251d8b..e86ceab3d 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -2804,9 +2804,10 @@ JSScript::assertValidJumpTargets() const for (; tn < tnlimit; tn++) { jsbytecode* tryStart = mainEntry + tn->start; jsbytecode* tryPc = tryStart - 1; - if (JSOp(*tryPc) != JSOP_TRY) + if (tn->kind != JSTRY_CATCH && tn->kind != JSTRY_FINALLY) continue; + MOZ_ASSERT(JSOp(*tryPc) == JSOP_TRY); jsbytecode* tryTarget = tryStart + tn->length; MOZ_ASSERT(mainEntry <= tryTarget && tryTarget < end); MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget))); -- cgit v1.2.3 From 34533e06a7b6f73bb9cf16058207e97bd91c832c Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 12:38:28 +0200 Subject: Bug 1332881 - Handle stack value in correct order when leaving loop and try-finally Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 6 +++ js/src/tests/ecma_6/Statements/for-inof-finally.js | 54 ++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 js/src/tests/ecma_6/Statements/for-inof-finally.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 98679eac6..bf7797d37 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2359,6 +2359,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } case StatementKind::ForOfLoop: + if (!flushPops(bce_)) + return false; + // The iterator and the current value are on the stack. // if (emitIteratorClose) { @@ -2378,6 +2381,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta break; case StatementKind::ForInLoop: + if (!flushPops(bce_)) + return false; + // The iterator and the current value are on the stack. if (!bce_->emit1(JSOP_POP)) // ... ITER return false; diff --git a/js/src/tests/ecma_6/Statements/for-inof-finally.js b/js/src/tests/ecma_6/Statements/for-inof-finally.js new file mode 100644 index 000000000..187dcdaf5 --- /dev/null +++ b/js/src/tests/ecma_6/Statements/for-inof-finally.js @@ -0,0 +1,54 @@ +var BUGNUMBER = 1332881; +var summary = + "Leaving for-in and try should handle stack value in correct order"; + +print(BUGNUMBER + ": " + summary); + +var a = (function () { + for (var x in [0]) { + try {} finally { + return 11; + } + } +})(); +assertEq(a, 11); + +var b = (function () { + for (var x of [0]) { + try {} finally { + return 12; + } + } +})(); +assertEq(b, 12); + +var c = (function () { + for (var x in [0]) { + for (var y of [0]) { + try {} finally { + return 13; + } + } + } +})(); +assertEq(c, 13); + +var d = (function () { + for (var x in [0]) { + for (var y of [0]) { + try {} finally { + for (var z in [0]) { + for (var w of [0]) { + try {} finally { + return 14; + } + } + } + } + } + } +})(); +assertEq(d, 14); + +if (typeof reportCompare === "function") + reportCompare(true, true); -- cgit v1.2.3 From e641a2c53a857141042ca62f1fe0b63b55a130af Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 12:56:18 +0200 Subject: Bug 1334799 - Handle stack value in correct order when leaving for-of loop from finally block Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 6 ++-- js/src/tests/ecma_6/Statements/for-inof-finally.js | 32 +++++++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index bf7797d37..76f1f75b5 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2396,6 +2396,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } } + if (!flushPops(bce_)) + return false; + if (target && target->is() && emitIteratorCloseAtTarget) { hasForOfLoopsWithIteratorClose = true; if (!target->as().finishIterCloseTryNote(bce_)) @@ -2417,9 +2420,6 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta return false; } - if (!flushPops(bce_)) - return false; - // See comment in ForOfLoopControl. if (hasForOfLoopsWithIteratorClose) { for (NestableControl* control = bce_->innermostNestableControl; diff --git a/js/src/tests/ecma_6/Statements/for-inof-finally.js b/js/src/tests/ecma_6/Statements/for-inof-finally.js index 187dcdaf5..e1f0c77e1 100644 --- a/js/src/tests/ecma_6/Statements/for-inof-finally.js +++ b/js/src/tests/ecma_6/Statements/for-inof-finally.js @@ -4,6 +4,24 @@ var summary = print(BUGNUMBER + ": " + summary); +var called = 0; +function reset() { + called = 0; +} +var obj = { + [Symbol.iterator]() { + return { + next() { + return { value: 10, done: false }; + }, + return() { + called++; + return {}; + } + }; + } +}; + var a = (function () { for (var x in [0]) { try {} finally { @@ -13,32 +31,37 @@ var a = (function () { })(); assertEq(a, 11); +reset(); var b = (function () { - for (var x of [0]) { + for (var x of obj) { try {} finally { return 12; } } })(); +assertEq(called, 1); assertEq(b, 12); +reset(); var c = (function () { for (var x in [0]) { - for (var y of [0]) { + for (var y of obj) { try {} finally { return 13; } } } })(); +assertEq(called, 1); assertEq(c, 13); +reset(); var d = (function () { for (var x in [0]) { - for (var y of [0]) { + for (var y of obj) { try {} finally { for (var z in [0]) { - for (var w of [0]) { + for (var w of obj) { try {} finally { return 14; } @@ -48,6 +71,7 @@ var d = (function () { } } })(); +assertEq(called, 2); assertEq(d, 14); if (typeof reportCompare === "function") -- cgit v1.2.3 From e2853b8a8fc05a7526d933a62828c26747b4580a Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 13:32:14 +0200 Subject: Bug 1322069 - Add TryEmitter Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 510 ++++++++++++++++++++++++------------ 1 file changed, 341 insertions(+), 169 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 76f1f75b5..b1b0055e2 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1563,6 +1563,318 @@ BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, return true; } +class MOZ_STACK_CLASS TryEmitter +{ + public: + enum Kind { + TryCatch, + TryCatchFinally, + TryFinally + }; + enum ShouldUseRetVal { + UseRetVal, + DontUseRetVal + }; + enum ShouldUseControl { + UseControl, + DontUseControl, + }; + + private: + BytecodeEmitter* bce_; + Kind kind_; + ShouldUseRetVal retValKind_; + + // Track jumps-over-catches and gosubs-to-finally for later fixup. + // + // When a finally block is active, non-local jumps (including + // jumps-over-catches) result in a GOSUB being written into the bytecode + // stream and fixed-up later. + // + // If ShouldUseControl is DontUseControl, all that handling is skipped. + // DontUseControl is used by yield*, that matches to the following: + // * has only one catch block + // * has no catch guard + // * has JSOP_GOTO at the end of catch-block + // * has no non-local-jump + // * doesn't use finally block for normal completion of try-block and + // catch-block + Maybe controlInfo_; + + int depth_; + unsigned noteIndex_; + ptrdiff_t tryStart_; + JumpList catchAndFinallyJump_; + JumpTarget tryEnd_; + JumpTarget finallyStart_; + + enum State { + Start, + Try, + TryEnd, + Catch, + CatchEnd, + Finally, + FinallyEnd, + End + }; + State state_; + + bool hasCatch() const { + return kind_ == TryCatch || kind_ == TryCatchFinally; + } + bool hasFinally() const { + return kind_ == TryCatchFinally || kind_ == TryFinally; + } + + public: + TryEmitter(BytecodeEmitter* bce, Kind kind, ShouldUseRetVal retValKind = UseRetVal, + ShouldUseControl controlKind = UseControl) + : bce_(bce), + kind_(kind), + retValKind_(retValKind), + depth_(0), + noteIndex_(0), + tryStart_(0), + state_(Start) + { + if (controlKind == UseControl) + controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try); + finallyStart_.offset = 0; + } + + bool emitJumpOverCatchAndFinally() { + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + return true; + } + + bool emitTry() { + MOZ_ASSERT(state_ == Start); + + // Since an exception can be thrown at any place inside the try block, + // we need to restore the stack and the scope chain before we transfer + // the control to the exception handler. + // + // For that we store in a try note associated with the catch or + // finally block the stack depth upon the try entry. The interpreter + // uses this depth to properly unwind the stack and the scope chain. + depth_ = bce_->stackDepth; + + // Record the try location, then emit the try block. + if (!bce_->newSrcNote(SRC_TRY, ¬eIndex_)) + return false; + if (!bce_->emit1(JSOP_TRY)) + return false; + tryStart_ = bce_->offset(); + + state_ = Try; + return true; + } + + private: + bool emitTryEnd() { + MOZ_ASSERT(state_ == Try); + MOZ_ASSERT(depth_ == bce_->stackDepth); + + // GOSUB to finally, if present. + if (hasFinally() && controlInfo_) { + if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) + return false; + } + + // Source note points to the jump at the end of the try block. + if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH)) + return false; + + // Emit jump over catch and/or finally. + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + + if (!bce_->emitJumpTarget(&tryEnd_)) + return false; + + return true; + } + + public: + bool emitCatch() { + if (state_ == Try) { + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == Catch); + if (!emitCatchEnd(true)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (retValKind_ == UseRetVal) { + // Clear the frame's return value that might have been set by the + // try block: + // + // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 + if (!bce_->emit1(JSOP_UNDEFINED)) + return false; + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + state_ = Catch; + return true; + } + + private: + bool emitCatchEnd(bool hasNext) { + MOZ_ASSERT(state_ == Catch); + + if (!controlInfo_) + return true; + + // gosub , if required. + if (hasFinally()) { + if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) + return false; + MOZ_ASSERT(bce_->stackDepth == depth_); + } + + // Jump over the remaining catch blocks. This will get fixed + // up to jump to after catch/finally. + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + + // If this catch block had a guard clause, patch the guard jump to + // come here. + if (controlInfo_->guardJump.offset != -1) { + if (!bce_->emitJumpTargetAndPatch(controlInfo_->guardJump)) + return false; + controlInfo_->guardJump.offset = -1; + + // If this catch block is the last one, rethrow, delegating + // execution of any finally block to the exception handler. + if (!hasNext) { + if (!bce_->emit1(JSOP_EXCEPTION)) + return false; + if (!bce_->emit1(JSOP_THROW)) + return false; + } + } + + return true; + } + + public: + bool emitFinally(Maybe finallyPos = Nothing()) { + MOZ_ASSERT(hasFinally()); + + if (state_ == Try) { + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == Catch); + if (!emitCatchEnd(false)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (!bce_->emitJumpTarget(&finallyStart_)) + return false; + + if (controlInfo_) { + // Fix up the gosubs that might have been emitted before non-local + // jumps to the finally code. + bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_); + + // Indicate that we're emitting a subroutine body. + controlInfo_->setEmittingSubroutine(); + } + if (finallyPos) { + if (!bce_->updateSourceCoordNotes(finallyPos.value())) + return false; + } + if (!bce_->emit1(JSOP_FINALLY)) + return false; + + if (retValKind_ == UseRetVal) { + if (!bce_->emit1(JSOP_GETRVAL)) + return false; + + // Clear the frame's return value to make break/continue return + // correct value even if there's no other statement before them: + // + // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 + if (!bce_->emit1(JSOP_UNDEFINED)) + return false; + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + state_ = Finally; + return true; + } + + private: + bool emitFinallyEnd() { + MOZ_ASSERT(state_ == Finally); + + if (retValKind_ == UseRetVal) { + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + if (!bce_->emit1(JSOP_RETSUB)) + return false; + + bce_->hasTryFinally = true; + return true; + } + + public: + bool emitEnd() { + if (state_ == Catch) { + MOZ_ASSERT(!hasFinally()); + if (!emitCatchEnd(false)) + return false; + } else { + MOZ_ASSERT(state_ == Finally); + MOZ_ASSERT(hasFinally()); + if (!emitFinallyEnd()) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + // ReconstructPCStack needs a NOP here to mark the end of the last + // catch block. + if (!bce_->emit1(JSOP_NOP)) + return false; + + // Fix up the end-of-try/catch jumps to come here. + if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) + return false; + + // Add the try note last, to let post-order give us the right ordering + // (first to last for a given nesting level, inner to outer by level). + if (hasCatch()) { + if (!bce_->tryNoteList.append(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset)) + return false; + } + + // If we've got a finally, mark try+catch region with additional + // trynote to catch exceptions (re)thrown from a catch block or + // for the try{}finally{} case. + if (hasFinally()) { + if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset)) + return false; + } + + state_ = End; + return true; + } +}; + class MOZ_STACK_CLASS IfThenElseEmitter { BytecodeEmitter* bce_; @@ -6073,57 +6385,28 @@ BytecodeEmitter::emitCatch(ParseNode* pn) MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(ParseNode* pn) { - // Track jumps-over-catches and gosubs-to-finally for later fixup. - // - // When a finally block is active, non-local jumps (including - // jumps-over-catches) result in a GOSUB being written into the bytecode - // stream and fixed-up later. - // - TryFinallyControl controlInfo(this, pn->pn_kid3 ? StatementKind::Finally : StatementKind::Try); - - // Since an exception can be thrown at any place inside the try block, - // we need to restore the stack and the scope chain before we transfer - // the control to the exception handler. - // - // For that we store in a try note associated with the catch or - // finally block the stack depth upon the try entry. The interpreter - // uses this depth to properly unwind the stack and the scope chain. - // - int depth = stackDepth; - - // Record the try location, then emit the try block. - unsigned noteIndex; - if (!newSrcNote(SRC_TRY, ¬eIndex)) - return false; - if (!emit1(JSOP_TRY)) - return false; - - ptrdiff_t tryStart = offset(); - if (!emitTree(pn->pn_kid1)) - return false; - MOZ_ASSERT(depth == stackDepth); + ParseNode* catchList = pn->pn_kid2; + ParseNode* finallyNode = pn->pn_kid3; - // GOSUB to finally, if present. - if (pn->pn_kid3) { - if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) - return false; + TryEmitter::Kind kind; + if (catchList) { + if (finallyNode) + kind = TryEmitter::TryCatchFinally; + else + kind = TryEmitter::TryCatch; + } else { + MOZ_ASSERT(finallyNode); + kind = TryEmitter::TryFinally; } + TryEmitter tryCatch(this, kind); - // Source note points to the jump at the end of the try block. - if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart + JSOP_TRY_LENGTH)) - return false; - - // Emit jump over catch and/or finally. - JumpList catchJump; - if (!emitJump(JSOP_GOTO, &catchJump)) + if (!tryCatch.emitTry()) return false; - JumpTarget tryEnd; - if (!emitJumpTarget(&tryEnd)) + if (!emitTree(pn->pn_kid1)) return false; // If this try has a catch block, emit it. - ParseNode* catchList = pn->pn_kid2; if (catchList) { MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); @@ -6153,110 +6436,26 @@ BytecodeEmitter::emitTry(ParseNode* pn) // capturing exceptions thrown from catch{} blocks. // for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) { - MOZ_ASSERT(this->stackDepth == depth); - - // Clear the frame's return value that might have been set by the - // try block: - // - // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) + if (!tryCatch.emitCatch()) return false; // Emit the lexical scope and catch body. MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); if (!emitTree(pn3)) return false; - - // gosub , if required. - if (pn->pn_kid3) { - if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) - return false; - MOZ_ASSERT(this->stackDepth == depth); - } - - // Jump over the remaining catch blocks. This will get fixed - // up to jump to after catch/finally. - if (!emitJump(JSOP_GOTO, &catchJump)) - return false; - - // If this catch block had a guard clause, patch the guard jump to - // come here. - if (controlInfo.guardJump.offset != -1) { - if (!emitJumpTargetAndPatch(controlInfo.guardJump)) - return false; - controlInfo.guardJump.offset = -1; - - // If this catch block is the last one, rethrow, delegating - // execution of any finally block to the exception handler. - if (!pn3->pn_next) { - if (!emit1(JSOP_EXCEPTION)) - return false; - if (!emit1(JSOP_THROW)) - return false; - } - } } } - MOZ_ASSERT(this->stackDepth == depth); - // Emit the finally handler, if there is one. - JumpTarget finallyStart{ 0 }; - if (pn->pn_kid3) { - if (!emitJumpTarget(&finallyStart)) - return false; - - // Fix up the gosubs that might have been emitted before non-local - // jumps to the finally code. - patchJumpsToTarget(controlInfo.gosubs, finallyStart); - - // Indicate that we're emitting a subroutine body. - controlInfo.setEmittingSubroutine(); - if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin)) - return false; - if (!emit1(JSOP_FINALLY)) - return false; - if (!emit1(JSOP_GETRVAL)) - return false; - - // Clear the frame's return value to make break/continue return - // correct value even if there's no other statement before them: - // - // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) + if (finallyNode) { + if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) return false; - if (!emitTree(pn->pn_kid3)) - return false; - if (!emit1(JSOP_SETRVAL)) - return false; - if (!emit1(JSOP_RETSUB)) + if (!emitTree(finallyNode)) return false; - hasTryFinally = true; - MOZ_ASSERT(this->stackDepth == depth); } - // ReconstructPCStack needs a NOP here to mark the end of the last catch block. - if (!emit1(JSOP_NOP)) - return false; - - // Fix up the end-of-try/catch jumps to come here. - if (!emitJumpTargetAndPatch(catchJump)) - return false; - - // Add the try note last, to let post-order give us the right ordering - // (first to last for a given nesting level, inner to outer by level). - if (catchList && !tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd.offset)) - return false; - - // If we've got a finally, mark try+catch region with additional - // trynote to catch exceptions (re)thrown from a catch block or - // for the try{}finally{} case. - if (pn->pn_kid3 && !tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart.offset)) + if (!tryCatch.emitEnd()) return false; return true; @@ -8040,17 +8239,15 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) int depth = stackDepth; MOZ_ASSERT(depth >= 2); - JumpList send; - if (!emitJump(JSOP_GOTO, &send)) // goto send + TryEmitter tryCatch(this, TryEmitter::TryCatchFinally, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + if (!tryCatch.emitJumpOverCatchAndFinally()) // ITER RESULT return false; - // Try prologue. // ITER RESULT - unsigned noteIndex; - if (!newSrcNote(SRC_TRY, ¬eIndex)) - return false; JumpTarget tryStart{ offset() }; - if (!emit1(JSOP_TRY)) // tryStart: + if (!tryCatch.emitTry()) // ITER RESULT return false; + MOZ_ASSERT(this->stackDepth == depth); // Load the generator object. @@ -8061,17 +8258,9 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED return false; - // Try epilogue. - if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart.offset)) - return false; - if (!emitJump(JSOP_GOTO, &send)) // goto send - return false; - - JumpTarget tryEnd; - if (!emitJumpTarget(&tryEnd)) // tryEnd: + if (!tryCatch.emitCatch()) // ITER RESULT return false; - // Catch location. stackDepth = uint32_t(depth); // ITER RESULT if (!emit1(JSOP_EXCEPTION)) // ITER RESULT EXCEPTION return false; @@ -8118,36 +8307,19 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult return false; - // The finally block, IteratorClose logic. + // IteratorClose logic. + if (!tryCatch.emitFinally()) + return false; - JumpTarget finallyStart{ 0 }; - if (!emitJumpTarget(&finallyStart)) - return false; - if (!emit1(JSOP_FINALLY)) // ITER RESULT FTYPE FVALUE - return false; if (!emitDupAt(3)) // ITER RESULT FTYPE FVALUE ITER return false; if (!emitIteratorClose(Some(tryStart))) // ITER RESULT FTYPE FVALUE return false; - if (!emit1(JSOP_RETSUB)) // ITER RESULT - return false; - - // Catch and finally epilogue. - // This is a peace offering to ReconstructPCStack. See the note in EmitTry. - if (!emit1(JSOP_NOP)) - return false; - size_t tryStartOffset = tryStart.offset + JSOP_TRY_LENGTH; - if (!tryNoteList.append(JSTRY_CATCH, depth, tryStartOffset, tryEnd.offset)) - return false; - if (!tryNoteList.append(JSTRY_FINALLY, depth, tryStartOffset, finallyStart.offset)) + if (!tryCatch.emitEnd()) return false; // After the try-catch-finally block: send the received value to the iterator. - if (!emitJumpTargetAndPatch(send)) // send: - return false; - - // Send location. // result = iter.next(received) // ITER RECEIVED if (!emit1(JSOP_SWAP)) // RECEIVED ITER return false; -- cgit v1.2.3 From f3c542a6a55a90a0aa860b306fbefe329c3faaa4 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 13:34:31 +0200 Subject: Bug 1333946 - Make IonBuilder::processIterators transitive Issue #74 --- js/src/jit-test/tests/ion/bug1333946.js | 6 +++++ js/src/jit/IonBuilder.cpp | 45 +++++++++++++++------------------ 2 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 js/src/jit-test/tests/ion/bug1333946.js (limited to 'js/src') diff --git a/js/src/jit-test/tests/ion/bug1333946.js b/js/src/jit-test/tests/ion/bug1333946.js new file mode 100644 index 000000000..9cb3fb629 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1333946.js @@ -0,0 +1,6 @@ +// |jit-test| exitstatus: 6; + +for (var x of [0]) { + timeout(0.001); + for (;;) {} +} diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 4318db2b6..534a48a90 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -958,35 +958,32 @@ IonBuilder::build() bool IonBuilder::processIterators() { - // Find phis that must directly hold an iterator live. - Vector worklist; + // Find and mark phis that must transitively hold an iterator live. + + Vector worklist; + for (size_t i = 0; i < iterators_.length(); i++) { - MDefinition* def = iterators_[i]; - if (def->isPhi()) { - if (!worklist.append(def->toPhi())) - return false; - } else { - for (MUseDefIterator iter(def); iter; iter++) { - if (iter.def()->isPhi()) { - if (!worklist.append(iter.def()->toPhi())) - return false; - } - } - } + if (!worklist.append(iterators_[i])) + return false; + iterators_[i]->setInWorklist(); } - // Propagate the iterator and live status of phis to all other connected - // phis. while (!worklist.empty()) { - MPhi* phi = worklist.popCopy(); - phi->setIterator(); - phi->setImplicitlyUsedUnchecked(); - - for (MUseDefIterator iter(phi); iter; iter++) { - if (iter.def()->isPhi()) { - MPhi* other = iter.def()->toPhi(); - if (!other->isIterator() && !worklist.append(other)) + MDefinition* def = worklist.popCopy(); + def->setNotInWorklist(); + + if (def->isPhi()) { + MPhi* phi = def->toPhi(); + phi->setIterator(); + phi->setImplicitlyUsedUnchecked(); + } + + for (MUseDefIterator iter(def); iter; iter++) { + MDefinition* use = iter.def(); + if (!use->isInWorklist() && (!use->isPhi() || !use->toPhi()->isIterator())) { + if (!worklist.append(use)) return false; + use->setInWorklist(); } } } -- cgit v1.2.3 From c93787917dbf305aa622d2d288effff78fe81008 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 13:35:18 +0200 Subject: Bug 1335996 - Make test for bug 1333946 more reliable Issue #74 --- js/src/jit-test/tests/ion/bug1333946.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/jit-test/tests/ion/bug1333946.js b/js/src/jit-test/tests/ion/bug1333946.js index 9cb3fb629..1fa1b9c49 100644 --- a/js/src/jit-test/tests/ion/bug1333946.js +++ b/js/src/jit-test/tests/ion/bug1333946.js @@ -1,6 +1,8 @@ -// |jit-test| exitstatus: 6; +// |jit-test| error: 42; for (var x of [0]) { - timeout(0.001); - for (;;) {} + for (var i = 0; ; i++) { + if (i === 20000) + throw 42; + } } -- cgit v1.2.3 From 4ee42e38e0a490eb4880b4a260e3cbe07dd486d1 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 14:20:11 +0200 Subject: Bug 1338796 - Do not call iterator.return if iterator.throw is present in yield* Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 280 ++++++++++++--------- js/src/frontend/BytecodeEmitter.h | 4 +- .../ecma_6/Generators/yield-star-iterator-close.js | 38 ++- js/src/tests/ecma_6/shell.js | 7 +- 4 files changed, 197 insertions(+), 132 deletions(-) (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index b1b0055e2..4a60f1cf1 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -5141,7 +5141,7 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) } bool -BytecodeEmitter::emitIteratorClose(Maybe yieldStarTryStart, bool allowSelfHosted) +BytecodeEmitter::emitIteratorClose(bool allowSelfHosted) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".close() on iterators is prohibited in self-hosted code because it " @@ -5180,86 +5180,11 @@ BytecodeEmitter::emitIteratorClose(Maybe yieldStarTryStart, bool all // an Object. if (!emit1(JSOP_SWAP)) // ... RET ITER return false; - - // ES 14.4.13, yield * AssignmentExpression, step 5.c - // - // When emitting iterator.return() for yield* forced return, we need to - // pass the argument passed to Generator.prototype.return to the return - // method. - if (yieldStarTryStart) { - IfThenElseEmitter ifGeneratorClosing(this); - if (!emitDupAt(2)) // ... FTYPE FVALUE RET ITER FVALUE - return false; - if (!emit1(JSOP_ISGENCLOSING)) // ... FTYPE FVALUE RET ITER FVALUE CLOSING - return false; - if (!emit1(JSOP_SWAP)) // ... FTYPE FVALUE RET ITER CLOSING FVALUE - return false; - if (!emit1(JSOP_POP)) // ... FTYPE FVALUE RET ITER CLOSING - return false; - if (!ifGeneratorClosing.emitIfElse()) // ... FTYPE FVALUE RET ITER - return false; - - if (!emit1(JSOP_GETRVAL)) // ... FTYPE FVALUE RET ITER RVAL - return false; - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... FTYPE FVALUE RET ITER VALUE - return false; - if (!emitCall(JSOP_CALL, 1)) // ... FTYPE FVALUE RESULT - return false; - checkTypeSet(JSOP_CALL); - if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... FTYPE FVALUE RESULT - return false; - - IfThenElseEmitter ifReturnDone(this); - if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT - return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RESULT DONE - return false; - if (!ifReturnDone.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE RESULT - return false; - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE VALUE - return false; - if (!emitPrepareIteratorResult()) // ITER OLDRESULT FTYPE FVALUE VALUE RESULT - return false; - if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RESULT VALUE - return false; - if (!emitFinishIteratorResult(true)) // ITER OLDRESULT FTYPE FVALUE RESULT - return false; - if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT - return false; - if (!emit1(JSOP_SETRVAL)) // ITER OLDRESULT FTYPE FVALUE RESULT - return false; - if (!ifReturnDone.emitElse()) // ITER OLDRESULT FTYPE FVALUE RESULT - return false; - int32_t savedDepth = this->stackDepth; - if (!emit2(JSOP_UNPICK, 3)) // ITER RESULT OLDRESULT FTYPE FVALUE - return false; - if (!emitUint16Operand(JSOP_POPN, 3)) // ITER RESULT - return false; - JumpList beq; - JumpTarget breakTarget{ -1 }; - if (!emitBackwardJump(JSOP_GOTO, *yieldStarTryStart, &beq, &breakTarget)) // ITER RESULT - return false; - this->stackDepth = savedDepth; - if (!ifReturnDone.emitEnd()) - return false; - - if (!ifGeneratorClosing.emitElse()) // ... FTYPE FVALUE RET ITER - return false; - if (!emitCall(JSOP_CALL, 0)) // ... FTYPE FVALUE RESULT - return false; - checkTypeSet(JSOP_CALL); - if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... FTYPE FVALUE RESULT - return false; - - if (!ifGeneratorClosing.emitEnd()) - return false; - } else { - if (!emitCall(JSOP_CALL, 0)) // ... RESULT - return false; - checkTypeSet(JSOP_CALL); - if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT - return false; - } + if (!emitCall(JSOP_CALL, 0)) // ... RESULT + return false; + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT + return false; if (!ifReturnMethodIsDefined.emitElse()) return false; @@ -8227,93 +8152,202 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator()); - if (!emitTree(iter)) // ITERABLE + if (!emitTree(iter)) // ITERABLE return false; - if (!emitIterator()) // ITER + if (!emitIterator()) // ITER return false; // Initial send value is undefined. - if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED + if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED return false; - int depth = stackDepth; - MOZ_ASSERT(depth >= 2); + int32_t savedDepthTemp; + int32_t startDepth = stackDepth; + MOZ_ASSERT(startDepth >= 2); TryEmitter tryCatch(this, TryEmitter::TryCatchFinally, TryEmitter::DontUseRetVal, TryEmitter::DontUseControl); - if (!tryCatch.emitJumpOverCatchAndFinally()) // ITER RESULT + if (!tryCatch.emitJumpOverCatchAndFinally()) // ITER RESULT return false; JumpTarget tryStart{ offset() }; - if (!tryCatch.emitTry()) // ITER RESULT + if (!tryCatch.emitTry()) // ITER RESULT return false; - MOZ_ASSERT(this->stackDepth == depth); + MOZ_ASSERT(this->stackDepth == startDepth); // Load the generator object. - if (!emitTree(gen)) // ITER RESULT GENOBJ + if (!emitTree(gen)) // ITER RESULT GENOBJ return false; // Yield RESULT as-is, without re-boxing. - if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED + if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED return false; - if (!tryCatch.emitCatch()) // ITER RESULT + if (!tryCatch.emitCatch()) // ITER RESULT return false; - stackDepth = uint32_t(depth); // ITER RESULT - if (!emit1(JSOP_EXCEPTION)) // ITER RESULT EXCEPTION + stackDepth = startDepth; // ITER RESULT + if (!emit1(JSOP_EXCEPTION)) // ITER RESULT EXCEPTION return false; - if (!emitDupAt(2)) // ITER RESULT EXCEPTION ITER + if (!emitDupAt(2)) // ITER RESULT EXCEPTION ITER return false; - if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER ITER + if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER ITER return false; - if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // ITER RESULT EXCEPTION ITER THROW + if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // ITER RESULT EXCEPTION ITER THROW return false; - if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER THROW THROW + if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER THROW THROW return false; - if (!emit1(JSOP_UNDEFINED)) // ITER RESULT EXCEPTION ITER THROW THROW UNDEFINED + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT EXCEPTION ITER THROW THROW UNDEFINED return false; - if (!emit1(JSOP_EQ)) // ITER RESULT EXCEPTION ITER THROW ?EQL + if (!emit1(JSOP_EQ)) // ITER RESULT EXCEPTION ITER THROW ?EQL return false; IfThenElseEmitter ifThrowMethodIsNotDefined(this); - if (!ifThrowMethodIsNotDefined.emitIf()) // ITER RESULT EXCEPTION ITER THROW + if (!ifThrowMethodIsNotDefined.emitIf()) // ITER RESULT EXCEPTION ITER THROW + return false; + savedDepthTemp = stackDepth; + if (!emit1(JSOP_POP)) // ITER RESULT EXCEPTION ITER + return false; + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.2 + // + // If the iterator does not have a "throw" method, it calls IteratorClose + // and then throws a TypeError. + if (!emitIteratorClose()) // ITER RESULT EXCEPTION return false; if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) // throw return false; - if (!ifThrowMethodIsNotDefined.emitEnd()) // ITER OLDRESULT EXCEPTION ITER THROW + stackDepth = savedDepthTemp; + if (!ifThrowMethodIsNotDefined.emitEnd()) // ITER OLDRESULT EXCEPTION ITER THROW return false; // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.4. - // RESULT = ITER.throw(EXCEPTION) // ITER OLDRESULT EXCEPTION ITER THROW - if (!emit1(JSOP_SWAP)) // ITER OLDRESULT EXCEPTION THROW ITER + // RESULT = ITER.throw(EXCEPTION) // ITER OLDRESULT EXCEPTION ITER THROW + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT EXCEPTION THROW ITER return false; - if (!emit2(JSOP_PICK, 2)) // ITER OLDRESULT THROW ITER EXCEPTION + if (!emit2(JSOP_PICK, 2)) // ITER OLDRESULT THROW ITER EXCEPTION return false; - if (!emitCall(JSOP_CALL, 1, iter)) // ITER OLDRESULT RESULT + if (!emitCall(JSOP_CALL, 1, iter)) // ITER OLDRESULT RESULT return false; checkTypeSet(JSOP_CALL); - if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) // ITER OLDRESULT RESULT + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) // ITER OLDRESULT RESULT return false; - if (!emit1(JSOP_SWAP)) // ITER RESULT OLDRESULT + if (!emit1(JSOP_SWAP)) // ITER RESULT OLDRESULT return false; - if (!emit1(JSOP_POP)) // ITER RESULT + if (!emit1(JSOP_POP)) // ITER RESULT return false; - MOZ_ASSERT(this->stackDepth == depth); + MOZ_ASSERT(this->stackDepth == startDepth); JumpList checkResult; - // Note that there is no GOSUB to the finally block here. If the iterator has a - // "throw" method, it does not perform IteratorClose per // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii. - if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult + // + // Note that there is no GOSUB to the finally block here. If the iterator has a + // "throw" method, it does not perform IteratorClose. + if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult return false; - // IteratorClose logic. if (!tryCatch.emitFinally()) return false; - if (!emitDupAt(3)) // ITER RESULT FTYPE FVALUE ITER + // ES 14.4.13, yield * AssignmentExpression, step 5.c + // + // Call iterator.return() for receiving a "forced return" completion from + // the generator. + + IfThenElseEmitter ifGeneratorClosing(this); + if (!emit1(JSOP_ISGENCLOSING)) // ITER RESULT FTYPE FVALUE CLOSING + return false; + if (!ifGeneratorClosing.emitIf()) // ITER RESULT FTYPE FVALUE + return false; + + // Step ii. + // + // Get the "return" method. + if (!emitDupAt(3)) // ITER RESULT FTYPE FVALUE ITER + return false; + if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER ITER + return false; + if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ITER RESULT FTYPE FVALUE ITER RET + return false; + + // Step iii. + // + // Do nothing if "return" is undefined. + IfThenElseEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER RET RET + return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT FTYPE FVALUE ITER RET RET UNDEFINED + return false; + if (!emit1(JSOP_NE)) // ITER RESULT FTYPE FVALUE ITER RET ?NEQL + return false; + + // Step iv. + // + // Call "return" with the argument passed to Generator.prototype.return, + // which is currently in rval.value. + if (!ifReturnMethodIsDefined.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE ITER RET + return false; + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RET ITER + return false; + if (!emit1(JSOP_GETRVAL)) // ITER OLDRESULT FTYPE FVALUE RET ITER RVAL + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RET ITER VALUE + return false; + if (!emitCall(JSOP_CALL, 1)) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + checkTypeSet(JSOP_CALL); + + // Step v. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + + // Steps vi-viii. + // + // Check if the returned object from iterator.return() is done. If not, + // continuing yielding. + IfThenElseEmitter ifReturnDone(this); + if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RESULT DONE + return false; + if (!ifReturnDone.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE VALUE + return false; + if (!emitPrepareIteratorResult()) // ITER OLDRESULT FTYPE FVALUE VALUE RESULT + return false; + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RESULT VALUE + return false; + if (!emitFinishIteratorResult(true)) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emit1(JSOP_SETRVAL)) // ITER OLDRESULT FTYPE FVALUE + return false; + savedDepthTemp = this->stackDepth; + if (!ifReturnDone.emitElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emit2(JSOP_UNPICK, 3)) // ITER RESULT OLDRESULT FTYPE FVALUE + return false; + if (!emitUint16Operand(JSOP_POPN, 3)) // ITER RESULT + return false; + { + // goto tryStart; + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_GOTO, tryStart, &beq, &breakTarget)) // ITER RESULT + return false; + } + this->stackDepth = savedDepthTemp; + if (!ifReturnDone.emitEnd()) + return false; + + if (!ifReturnMethodIsDefined.emitElse()) // ITER RESULT FTYPE FVALUE ITER RET + return false; + if (!emit1(JSOP_POP)) // ITER RESULT FTYPE FVALUE ITER return false; - if (!emitIteratorClose(Some(tryStart))) // ITER RESULT FTYPE FVALUE + if (!emit1(JSOP_POP)) // ITER RESULT FTYPE FVALUE + return false; + if (!ifReturnMethodIsDefined.emitEnd()) + return false; + + if (!ifGeneratorClosing.emitEnd()) return false; if (!tryCatch.emitEnd()) @@ -8338,7 +8372,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ITER RESULT return false; checkTypeSet(JSOP_CALL); - MOZ_ASSERT(this->stackDepth == depth); + MOZ_ASSERT(this->stackDepth == startDepth); if (!emitJumpTargetAndPatch(checkResult)) // checkResult: return false; @@ -8349,10 +8383,12 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; // if (!DONE) goto tryStart; - JumpList beq; - JumpTarget breakTarget{ -1 }; - if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) // ITER RESULT - return false; + { + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) // ITER RESULT + return false; + } // result.value if (!emit1(JSOP_SWAP)) // RESULT ITER @@ -8362,7 +8398,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // VALUE return false; - MOZ_ASSERT(this->stackDepth == depth - 1); + MOZ_ASSERT(this->stackDepth == startDepth - 1); return true; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 78eb3510c..2ab6c6fef 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -679,9 +679,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false); - MOZ_MUST_USE bool emitIteratorClose( - mozilla::Maybe yieldStarTryStart = mozilla::Nothing(), - bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorClose(bool allowSelfHosted = false); template MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, diff --git a/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js b/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js index ec62dd86d..91ad31cb6 100644 --- a/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js +++ b/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js @@ -6,6 +6,8 @@ function test() { var returnCalledExpected = 0; var nextCalled = 0; var nextCalledExpected = 0; + var throwCalled = 0; + var throwCalledExpected = 0; var iterable = {}; iterable[Symbol.iterator] = makeIterator({ next: function() { @@ -25,9 +27,9 @@ function test() { // G.p.throw on an iterator without "throw" calls IteratorClose. var g1 = y(); g1.next(); - assertThrowsValue(function() { + assertThrowsInstanceOf(function() { g1.throw("foo"); - }, "foo"); + }, TypeError); assertEq(returnCalled, ++returnCalledExpected); assertEq(nextCalled, ++nextCalledExpected); g1.next(); @@ -98,9 +100,18 @@ function test() { // IteratorClose expects iter.return to return an Object. var g6 = y(); g6.next(); - assertThrowsInstanceOf(function() { + var exc; + try { g6.throw("foo"); - }, TypeError); + } catch (e) { + exc = e; + } finally { + assertEq(exc instanceof TypeError, true); + // The message test is here because instanceof TypeError doesn't + // distinguish the non-Object return TypeError and the + // throw-method-is-not-defined iterator protocol error. + assertEq(exc.toString().indexOf("non-object") > 0, true); + } assertEq(returnCalled, ++returnCalledExpected); // G.p.return passes its argument to "return". @@ -115,6 +126,25 @@ function test() { g7.next(); g7.return("in test"); assertEq(returnCalled, ++returnCalledExpected); + + // If a throw method is present, do not call "return". + iterable[Symbol.iterator] = makeIterator({ + throw: function(e) { + throwCalled++; + throw e; + }, + ret: function(x) { + returnCalled++; + return { done: true }; + } + }); + var g8 = y(); + g8.next(); + assertThrowsValue(function() { + g8.throw("foo"); + }, "foo"); + assertEq(throwCalled, ++throwCalledExpected); + assertEq(returnCalled, returnCalledExpected); } test(); diff --git a/js/src/tests/ecma_6/shell.js b/js/src/tests/ecma_6/shell.js index 4da9221d6..756da9f36 100644 --- a/js/src/tests/ecma_6/shell.js +++ b/js/src/tests/ecma_6/shell.js @@ -21,10 +21,11 @@ /** Make an iterator with a return method. */ global.makeIterator = function makeIterator(overrides) { + var throwMethod; + if (overrides && overrides.throw) + throwMethod = overrides.throw; var iterator = { - throw: function(e) { - throw e; - }, + throw: throwMethod, next: function(x) { if (overrides && overrides.next) return overrides.next(x); -- cgit v1.2.3 From 6056525ced07af6c6c4f48ea605a2f4589821fdf Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 14:47:39 +0200 Subject: Bug 1341339 - Check for duplicates in processIterators Issue #74 --- js/src/jit-test/tests/for-of/bug-1341339.js | 9 +++++++++ js/src/jit/IonBuilder.cpp | 9 ++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 js/src/jit-test/tests/for-of/bug-1341339.js (limited to 'js/src') diff --git a/js/src/jit-test/tests/for-of/bug-1341339.js b/js/src/jit-test/tests/for-of/bug-1341339.js new file mode 100644 index 000000000..1f88acdaf --- /dev/null +++ b/js/src/jit-test/tests/for-of/bug-1341339.js @@ -0,0 +1,9 @@ +let m = parseModule(` +function* values() {} +var iterator = values(); +for (var i=0; i < 10000; ++i) { + for (var x of iterator) {} +} +`); +m.declarationInstantiation(); +m.evaluation(); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 534a48a90..26bba0656 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -963,9 +963,12 @@ IonBuilder::processIterators() Vector worklist; for (size_t i = 0; i < iterators_.length(); i++) { - if (!worklist.append(iterators_[i])) - return false; - iterators_[i]->setInWorklist(); + MDefinition* iter = iterators_[i]; + if (!iter->isInWorklist()) { + if (!worklist.append(iter)) + return false; + iter->setInWorklist(); + } } while (!worklist.empty()) { -- cgit v1.2.3 From e7a220aae2dd6f92c57b92fdd08ff94e5826c035 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 14:48:34 +0200 Subject: Bug 1331585 - Allow falsy "done" values for IteratorClose due to exception during array destructuring Issue #74 --- js/src/jit/JitFrames.cpp | 12 ++++++------ .../tests/ecma_6/Destructuring/array-iterator-close.js | 16 ++++++++++++++++ js/src/vm/Interpreter.cpp | 5 +++-- 3 files changed, 25 insertions(+), 8 deletions(-) (limited to 'js/src') diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 30bbd0b9b..7c3f0d120 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -353,11 +353,11 @@ CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* RootedObject iterObject(cx, &v.toObject()); if (isDestructuring) { - Value v = si.read(); - MOZ_ASSERT(v.isBoolean()); + RootedValue doneValue(cx, si.read()); + bool done = ToBoolean(doneValue); // Do not call IteratorClose if the destructuring iterator is already // done. - if (v.isTrue()) + if (done) return; } @@ -654,9 +654,9 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen uint8_t* framePointer; uint8_t* stackPointer; BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); - Value doneValue(*(reinterpret_cast(stackPointer))); - MOZ_ASSERT(doneValue.isBoolean()); - if (doneValue.isFalse()) { + RootedValue doneValue(cx, *(reinterpret_cast(stackPointer))); + bool done = ToBoolean(doneValue); + if (!done) { Value iterValue(*(reinterpret_cast(stackPointer) + 1)); RootedObject iterObject(cx, &iterValue.toObject()); if (!IteratorCloseForException(cx, iterObject)) { diff --git a/js/src/tests/ecma_6/Destructuring/array-iterator-close.js b/js/src/tests/ecma_6/Destructuring/array-iterator-close.js index f7805540d..ed35135db 100644 --- a/js/src/tests/ecma_6/Destructuring/array-iterator-close.js +++ b/js/src/tests/ecma_6/Destructuring/array-iterator-close.js @@ -43,6 +43,22 @@ function test() { }, "in lhs"); assertEq(returnCalled, ++returnCalledExpected); + // throw in lhs ref calls IteratorClose with falsy "done". + iterable[Symbol.iterator] = makeIterator({ + next: function() { + // "done" is undefined. + return {}; + }, + ret: function() { + returnCalled++; + return {}; + } + }); + assertThrowsValue(function() { + 0, [...{}[throwlhs()]] = iterable; + }, "in lhs"); + assertEq(returnCalled, ++returnCalledExpected); + // throw in iter.next doesn't call IteratorClose iterable[Symbol.iterator] = makeIterator({ next: function() { diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 9a8c6777f..7f8ff8445 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1205,8 +1205,9 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) // stack. The iterator object is second from the top. MOZ_ASSERT(tn->stackDepth > 1); Value* sp = regs.spForStackDepth(tn->stackDepth); - MOZ_ASSERT(sp[-1].isBoolean()); - if (sp[-1].isFalse()) { + RootedValue doneValue(cx, sp[-1]); + bool done = ToBoolean(doneValue); + if (!done) { RootedObject iterObject(cx, &sp[-2].toObject()); if (!IteratorCloseForException(cx, iterObject)) { SettleOnTryNote(cx, tn, ei, regs); -- cgit v1.2.3 From 2818e8dedb4a9a6cf40688ad42bf2be05e2f26d2 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 14:49:32 +0200 Subject: Bug 1334314 - Fix debug mode OSR exception handling for IteratorClose trynotes Issue #74 --- js/src/jit-test/tests/ion/bug1334314.js | 16 +++++++++++++ js/src/jit/BaselineBailouts.cpp | 41 +++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 js/src/jit-test/tests/ion/bug1334314.js (limited to 'js/src') diff --git a/js/src/jit-test/tests/ion/bug1334314.js b/js/src/jit-test/tests/ion/bug1334314.js new file mode 100644 index 000000000..488fc9027 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1334314.js @@ -0,0 +1,16 @@ +// |jit-test| error: TypeError + +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () { };"); + +function f() { + [[]] = []; +} +try { + f(); +} catch (e) {}; +try { + f(); +} catch (e) {}; +f(); diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 8fc8a522d..161e1aa4c 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -487,7 +487,7 @@ GetNextNonLoopEntryPc(jsbytecode* pc) } static bool -HasLiveIteratorAtStackDepth(JSScript* script, jsbytecode* pc, uint32_t stackDepth) +HasLiveStackValueAtDepth(JSScript* script, jsbytecode* pc, uint32_t stackDepth) { if (!script->hasTrynotes()) return false; @@ -501,14 +501,37 @@ HasLiveIteratorAtStackDepth(JSScript* script, jsbytecode* pc, uint32_t stackDept if (pcOffset >= tn->start + tn->length) continue; - // For-in loops have only the iterator on stack. - if (tn->kind == JSTRY_FOR_IN && stackDepth == tn->stackDepth) - return true; + switch (tn->kind) { + case JSTRY_FOR_IN: + // For-in loops have only the iterator on stack. + if (stackDepth == tn->stackDepth) + return true; + break; + + case JSTRY_FOR_OF: + // For-of loops have both the iterator and the result object on + // stack. The iterator is below the result object. + if (stackDepth == tn->stackDepth - 1) + return true; + break; + + case JSTRY_ITERCLOSE: + // Code that need to call IteratorClose have the iterator on the + // stack. + if (stackDepth == tn->stackDepth) + return true; + break; - // For-of loops have both the iterator and the result object on - // stack. The iterator is below the result object. - if (tn->kind == JSTRY_FOR_OF && stackDepth == tn->stackDepth - 1) - return true; + case JSTRY_DESTRUCTURING_ITERCLOSE: + // Destructuring code that need to call IteratorClose have both + // the iterator and the "done" value on the stack. + if (stackDepth == tn->stackDepth || stackDepth == tn->stackDepth - 1) + return true; + break; + + default: + break; + } } return false; @@ -945,7 +968,7 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, // iterators, however, so read them out. They will be closed by // HandleExceptionBaseline. MOZ_ASSERT(cx->compartment()->isDebuggee()); - if (iter.moreFrames() || HasLiveIteratorAtStackDepth(script, pc, i + 1)) { + if (iter.moreFrames() || HasLiveStackValueAtDepth(script, pc, i + 1)) { v = iter.read(); } else { iter.skip(); -- cgit v1.2.3 From b311e8fa718e2a32805c25847781cc8a6a0541bd Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 16:22:38 +0200 Subject: Bug 1342553, Bug 1343072, Bug 1344753 (details in the description) Bug 1342553 - Part 0.1: Use try-catch for IteratorClose in for-of Bug 1343072 - Update HasLiveStackValueAtDepth to follow the change in JSTRY_FOR_OF Bug 1344753 - Update for-of stack depth in ControlFlowGenerator::processWhileOrForInLoop Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 453 ++++++++++++++------- js/src/frontend/BytecodeEmitter.h | 6 +- .../auto-regress/for-of-iterator-close-debugger.js | 15 + js/src/jit/BaselineBailouts.cpp | 14 +- js/src/jit/JitFrames.cpp | 15 - js/src/jsapi.h | 9 + js/src/jsscript.h | 1 - js/src/shell/js.cpp | 2 - js/src/vm/Interpreter.cpp | 34 +- js/src/vm/Interpreter.h | 7 + js/src/vm/Opcodes.h | 11 +- 11 files changed, 378 insertions(+), 189 deletions(-) create mode 100644 js/src/jit-test/tests/auto-regress/for-of-iterator-close-debugger.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4a60f1cf1..4d6ff6305 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -244,9 +244,9 @@ class LoopControl : public BreakableControl loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; int loopSlots; - if (loopKind == StatementKind::Spread) + if (loopKind == StatementKind::Spread || loopKind == StatementKind::ForOfLoop) loopSlots = 3; - else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop) + else if (loopKind == StatementKind::ForInLoop) loopSlots = 2; else loopSlots = 0; @@ -278,64 +278,6 @@ class LoopControl : public BreakableControl } }; -class ForOfLoopControl : public LoopControl -{ - // The stack depth of the iterator. - int32_t iterDepth_; - - // for-of loops, when throwing from non-iterator code (i.e. from the body - // or from evaluating the LHS of the loop condition), need to call - // IteratorClose. If IteratorClose itself throws, we must not re-call - // IteratorClose. Since non-local jumps like break and return call - // IteratorClose, whenever a non-local jump is emitted, we must terminate - // the current JSTRY_ITERCLOSE note to skip the non-local jump code, then - // start a new one. - // - // Visually, - // - // for (x of y) { - // ... instantiate ForOfLoopControl - // ... + <-- iterCloseTryStart_ points to right before - // ... assignment to loop variable - // ... ^ - // ... | - // if (...) v - // + call finishIterCloseTryNote before |break| - // above range is noted with JSTRY_ITERCLOSE - // - // break; <-- break and IteratorClose are not inside - // JSTRY_ITERCLOSE note - // - // call startNewIterCloseTryNote after |break| - // + <-- next iterCloseTryStart_ points here - // ... | - // ... ~ - // } - ptrdiff_t iterCloseTryStart_; - - public: - ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth) - : LoopControl(bce, StatementKind::ForOfLoop), - iterDepth_(iterDepth), - iterCloseTryStart_(-1) - { - MOZ_ASSERT(bce->stackDepth >= iterDepth); - } - - MOZ_MUST_USE bool finishIterCloseTryNote(BytecodeEmitter* bce) { - ptrdiff_t end = bce->offset(); - MOZ_ASSERT(end >= iterCloseTryStart_); - if (end != iterCloseTryStart_) - return bce->tryNoteList.append(JSTRY_ITERCLOSE, iterDepth_, iterCloseTryStart_, end); - return true; - } - - void startNewIterCloseTryNote(BytecodeEmitter* bce) { - MOZ_ASSERT(bce->offset() > iterCloseTryStart_); - iterCloseTryStart_ = bce->offset(); - } -}; - class TryFinallyControl : public BytecodeEmitter::NestableControl { bool emittingSubroutine_; @@ -2025,6 +1967,144 @@ class MOZ_STACK_CLASS IfThenElseEmitter #endif }; +class ForOfLoopControl : public LoopControl +{ + // The stack depth of the iterator. + int32_t iterDepth_; + + // for-of loops, when throwing from non-iterator code (i.e. from the body + // or from evaluating the LHS of the loop condition), need to call + // IteratorClose. This is done by enclosing non-iterator code with + // try-catch and call IteratorClose in `catch` block. + // If IteratorClose itself throws, we must not re-call IteratorClose. Since + // non-local jumps like break and return call IteratorClose, whenever a + // non-local jump is emitted, we must tell catch block not to perform + // IteratorClose. + // + // for (x of y) { + // // Operations for iterator (IteratorNext etc) are outside of + // // try-block. + // try { + // ... + // if (...) { + // // Before non-local jump, clear iterator on the stack to tell + // // catch block not to perform IteratorClose. + // tmpIterator = iterator; + // iterator = undefined; + // IteratorClose(tmpIterator, { break }); + // break; + // } + // ... + // } catch (e) { + // // Just throw again when iterator is cleared by non-local jump. + // if (iterator === undefined) + // throw e; + // IteratorClose(iterator, { throw, e }); + // } + // } + Maybe tryCatch_; + + bool allowSelfHosted_; + + public: + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted) + : LoopControl(bce, StatementKind::ForOfLoop), + iterDepth_(iterDepth), + allowSelfHosted_(allowSelfHosted) + { + } + + bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { + tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal); + + if (!tryCatch_->emitTry()) + return false; + return true; + } + + bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) { + if (!tryCatch_->emitCatch()) // ITER ... + return false; + + if (!bce->emit1(JSOP_EXCEPTION)) // ITER ... EXCEPTION + return false; + unsigned slotFromTop = bce->stackDepth - iterDepth_; + if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER + return false; + + // If ITER is undefined, it means the exception is thrown by + // IteratorClose for non-local jump, and we should't perform + // IteratorClose again here. + if (!bce->emit1(JSOP_UNDEFINED)) // ITER ... EXCEPTION ITER UNDEF + return false; + if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE + return false; + + IfThenElseEmitter ifIteratorIsNotClosed(bce); + if (!ifIteratorIsNotClosed.emitIf()) // ITER ... EXCEPTION + return false; + + MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_)); + if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER + return false; + if (!emitIteratorClose(bce, CompletionKind::Throw)) // ITER ... EXCEPTION + return false; + + if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION + return false; + + if (!bce->emit1(JSOP_THROW)) // ITER ... + return false; + + if (!tryCatch_->emitEnd()) + return false; + + tryCatch_.reset(); + return true; + } + + bool emitIteratorClose(BytecodeEmitter* bce, + CompletionKind completionKind = CompletionKind::Normal) { + return bce->emitIteratorClose(completionKind, allowSelfHosted_); + } + + bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) { + // Pop unnecessary values from the stack. Effectively this means + // leaving try-catch block. However, the performing IteratorClose can + // reach the depth for try-catch, and effectively re-enter the + // try-catch block. + if (!bce->emit1(JSOP_POP)) // ITER RESULT + return false; + if (!bce->emit1(JSOP_POP)) // ITER + return false; + + // Clear ITER slot on the stack to tell catch block to avoid performing + // IteratorClose again. + if (!bce->emit1(JSOP_UNDEFINED)) // ITER UNDEF + return false; + if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER + return false; + + if (!emitIteratorClose(bce)) // UNDEF + return false; + + if (isTarget) { + // At the level of the target block, there's bytecode after the + // loop that will pop the iterator and the value, so push + // undefineds to balance the stack. + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF + return false; + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF UNDEF + return false; + } else { + if (!bce->emit1(JSOP_POP)) // + return false; + } + + return true; + } +}; + BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, @@ -2383,6 +2463,12 @@ BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) return emit2(JSOP_CHECKISOBJ, uint8_t(kind)); } +bool +BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind) +{ + return emit2(JSOP_CHECKISCALLABLE, uint8_t(kind)); +} + static inline unsigned LengthOfSetLine(unsigned line) { @@ -2624,10 +2710,8 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta EmitterScope* es = bce_->innermostEmitterScope; int npops = 0; - bool hasForOfLoopsWithIteratorClose = false; - // IteratorClose is handled specially in the exception unwinder. For - // 'continue', 'break', and 'return' statements, emit IteratorClose + // For 'continue', 'break', and 'return' statements, emit IteratorClose // bytecode inline. 'continue' statements do not call IteratorClose for // the loop they are continuing. bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return; @@ -2664,31 +2748,22 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } else { if (!flushPops(bce_)) return false; - if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) + if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) // ... return false; } break; } case StatementKind::ForOfLoop: - if (!flushPops(bce_)) - return false; - - // The iterator and the current value are on the stack. - // if (emitIteratorClose) { - hasForOfLoopsWithIteratorClose = true; - if (!control->as().finishIterCloseTryNote(bce_)) - return false; - if (!bce_->emit1(JSOP_POP)) // ... ITER + if (!flushPops(bce_)) return false; - if (!bce_->emitIteratorClose()) // ... + + ForOfLoopControl& loopinfo = control->as(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ false)) // ... return false; } else { - if (!bce_->emit1(JSOP_POP)) // ... ITER - return false; - if (!bce_->emit1(JSOP_POP)) // ... - return false; + npops += 3; } break; @@ -2711,18 +2786,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta if (!flushPops(bce_)) return false; - if (target && target->is() && emitIteratorCloseAtTarget) { - hasForOfLoopsWithIteratorClose = true; - if (!target->as().finishIterCloseTryNote(bce_)) - return false; - - // The iterator and the current value are on the stack. At the level - // of the target block, there's bytecode after the loop that will pop - // the iterator and the value, so duplicate the iterator and call - // IteratorClose. - if (!bce_->emitDupAt(1)) // ... ITER RESULT ITER - return false; - if (!bce_->emitIteratorClose()) // ... ITER RESULT + if (target && emitIteratorCloseAtTarget && target->is()) { + ForOfLoopControl& loopinfo = target->as(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ true)) // ... UNDEF UNDEF UNDEF return false; } @@ -2732,20 +2798,6 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta return false; } - // See comment in ForOfLoopControl. - if (hasForOfLoopsWithIteratorClose) { - for (NestableControl* control = bce_->innermostNestableControl; - control != target; - control = control->enclosing()) - { - if (control->is()) - control->as().startNewIterCloseTryNote(bce_); - } - - if (target && target->is() && emitIteratorCloseAtTarget) - target->as().startNewIterCloseTryNote(bce_); - } - return true; } @@ -5120,7 +5172,7 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri } bool -BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) +BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".next() iteration is prohibited in self-hosted code because it " @@ -5141,7 +5193,8 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) } bool -BytecodeEmitter::emitIteratorClose(bool allowSelfHosted) +BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = CompletionKind::Normal */, + bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".close() on iterators is prohibited in self-hosted code because it " @@ -5174,17 +5227,86 @@ BytecodeEmitter::emitIteratorClose(bool allowSelfHosted) if (!ifReturnMethodIsDefined.emitIfElse()) return false; + if (completionKind == CompletionKind::Throw) { + // 7.4.6 IteratorClose ( iterator, completion ) + // ... + // 3. Let return be ? GetMethod(iterator, "return"). + // 4. If return is undefined, return Completion(completion). + // 5. Let innerResult be Call(return, iterator, « »). + // 6. If completion.[[Type]] is throw, return Completion(completion). + // 7. If innerResult.[[Type]] is throw, return + // Completion(innerResult). + // + // For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET + // is callable, and throws if not. Since step 6 doesn't match and + // error handling in step 3 and step 7 can be merged. + // + // For CompletionKind::Throw case, an error thrown by JSOP_CALL for + // step 5 is ignored by try-catch. So we should check if RET is + // callable here, outside of try-catch, and the throw immediately if + // not. + CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn; + if (!emitCheckIsCallable(kind)) // ... ITER RET + return false; + } + // Steps 5, 8. // // Call "return" if it is not undefined or null, and check that it returns // an Object. if (!emit1(JSOP_SWAP)) // ... RET ITER return false; - if (!emitCall(JSOP_CALL, 0)) // ... RESULT + + Maybe tryCatch; + + if (completionKind == CompletionKind::Throw) { + tryCatch.emplace(this, TryEmitter::TryCatch, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + + // Mutate stack to balance stack for try-catch. + if (!emit1(JSOP_UNDEFINED)) // ... RET ITER UNDEF + return false; + if (!tryCatch->emitTry()) // ... RET ITER UNDEF + return false; + if (!emitDupAt(2)) // ... RET ITER UNDEF RET + return false; + if (!emitDupAt(2)) // ... RET ITER UNDEF RET ITER + return false; + } + + if (!emitCall(JSOP_CALL, 0)) // ... ... RESULT return false; checkTypeSet(JSOP_CALL); - if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT - return false; + + if (completionKind == CompletionKind::Throw) { + if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF + return false; + if (!emit1(JSOP_POP)) // ... RET ITER RESULT + return false; + + if (!tryCatch->emitCatch()) // ... RET ITER RESULT + return false; + + // Just ignore the exception thrown by call. + if (!emit1(JSOP_EXCEPTION)) // ... RET ITER RESULT EXC + return false; + if (!emit1(JSOP_POP)) // ... RET ITER RESULT + return false; + + if (!tryCatch->emitEnd()) // ... RET ITER RESULT + return false; + + // Restore stack. + if (!emit2(JSOP_UNPICK, 2)) // ... RESULT RET ITER + return false; + if (!emit1(JSOP_POP)) // ... RESULT RET + return false; + if (!emit1(JSOP_POP)) // ... RESULT + return false; + } else { + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT + return false; + } if (!ifReturnMethodIsDefined.emitElse()) return false; @@ -6749,8 +6871,19 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte MOZ_ASSERT(forOfHead->isKind(PNK_FOROF)); MOZ_ASSERT(forOfHead->isArity(PN_TERNARY)); - // Evaluate the expression being iterated. ParseNode* forHeadExpr = forOfHead->pn_kid3; + + // Certain builtins (e.g. Array.from) are implemented in self-hosting + // as for-of loops. + bool allowSelfHostedIter = false; + if (emitterMode == BytecodeEmitter::SelfHosting && + forHeadExpr->isKind(PNK_CALL) && + forHeadExpr->pn_head->name() == cx->names().allowContentIter) + { + allowSelfHostedIter = true; + } + + // Evaluate the expression being iterated. if (!emitTree(forHeadExpr)) // ITERABLE return false; if (!emitIterator()) // ITER @@ -6758,12 +6891,14 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte int32_t iterDepth = stackDepth; - // For-of loops have both the iterator and the value on the stack. Push - // undefined to balance the stack. + // For-of loops have both the iterator, the result, and the result.value + // on the stack. Push undefineds to balance the stack. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; - ForOfLoopControl loopInfo(this, iterDepth); + ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; @@ -6771,11 +6906,11 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte return false; JumpList initialJump; - if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT + if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT UNDEF return false; JumpTarget top{ -1 }; - if (!emitLoopHead(nullptr, &top)) // ITER RESULT + if (!emitLoopHead(nullptr, &top)) // ITER RESULT UNDEF return false; // If the loop had an escaping lexical declaration, replace the current @@ -6792,7 +6927,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { - if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT + if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT UNDEF return false; } @@ -6812,61 +6947,66 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte // // Note that ES 13.7.5.13, step 5.c says getting result.value does not // call IteratorClose, so start JSTRY_ITERCLOSE after the GETPROP. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; - loopInfo.startNewIterCloseTryNote(this); - - if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE + if (!loopInfo.emitBeginCodeNeedingIteratorClose(this)) return false; - if (!emit1(JSOP_POP)) // ITER RESULT + if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE return false; MOZ_ASSERT(stackDepth == loopDepth, "the stack must be balanced around the initializing " "operation"); + // Remove VALUE from the stack to release it. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; + // Perform the loop body. ParseNode* forBody = forOfLoop->pn_right; - if (!emitTree(forBody)) // ITER RESULT + if (!emitTree(forBody)) // ITER RESULT UNDEF return false; - if (!loopInfo.finishIterCloseTryNote(this)) + MOZ_ASSERT(stackDepth == loopDepth, + "the stack must be balanced around the for-of body"); + + if (!loopInfo.emitEndCodeNeedingIteratorClose(this)) return false; // Set offset for continues. loopInfo.continueTarget = { offset() }; - if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT + if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT UNDEF return false; - if (!emit1(JSOP_POP)) // ITER + if (!emit1(JSOP_SWAP)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_POP)) // ITER UNDEF return false; - if (!emit1(JSOP_DUP)) // ITER ITER + if (!emitDupAt(1)) // ITER UNDEF ITER return false; - // Certain builtins (e.g. Array.from) are implemented in self-hosting - // as for-of loops. - bool allowSelfHostedIter = false; - if (emitterMode == BytecodeEmitter::SelfHosting && - forHeadExpr->isKind(PNK_CALL) && - forHeadExpr->pn_head->name() == cx->names().allowContentIter) - { - allowSelfHostedIter = true; - } + if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER UNDEF RESULT + return false; - if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER RESULT + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) - return false; // ITER RESULT + return false; // ITER RESULT UNDEF MOZ_ASSERT(this->stackDepth == loopDepth); } @@ -6881,7 +7021,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) return false; - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool @@ -7291,6 +7431,8 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) // Push a dummy result so that we properly enter iteration midstream. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT VALUE + return false; // Enter the block before the loop body, after evaluating the obj. // Initialize let bindings with undefined when entering, as the name @@ -7328,42 +7470,59 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) #endif // Emit code to assign result.value to the iteration variable. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; + + // Notice: Comprehension for-of doesn't perform IteratorClose, since it's + // not in the spec. + if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE return false; + + // Remove VALUE from the stack to release it. if (!emit1(JSOP_POP)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; // The stack should be balanced around the assignment opcode sequence. MOZ_ASSERT(this->stackDepth == loopDepth); // Emit code for the loop body. - if (!emitTree(forBody)) + if (!emitTree(forBody)) // ITER RESULT UNDEF return false; + // The stack should be balanced around the assignment opcode sequence. + MOZ_ASSERT(this->stackDepth == loopDepth); + // Set offset for continues. loopInfo.continueTarget = { offset() }; if (!emitLoopEntry(forHeadExpr, jmp)) return false; - if (!emit1(JSOP_POP)) // ITER + if (!emit1(JSOP_SWAP)) // ITER UNDEF RESULT return false; - if (!emit1(JSOP_DUP)) // ITER ITER + if (!emit1(JSOP_POP)) // ITER UNDEF return false; - if (!emitIteratorNext(forHead)) // ITER RESULT + if (!emitDupAt(1)) // ITER UNDEF ITER return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + if (!emitIteratorNext(forHead)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF + return false; + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; JumpList beq; JumpTarget breakTarget{ -1 }; - if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT UNDEF return false; MOZ_ASSERT(this->stackDepth == loopDepth); @@ -7385,7 +7544,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) } // Pop the result and the iter. - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 2ab6c6fef..7ff40b462 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -473,6 +473,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Helper to emit JSOP_CHECKISOBJ. MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind); + // Helper to emit JSOP_CHECKISCALLABLE. + MOZ_MUST_USE bool emitCheckIsCallable(CheckIsCallableKind kind); + // Emit a bytecode followed by an uint16 immediate operand stored in // big-endian order. MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand); @@ -679,7 +682,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false); - MOZ_MUST_USE bool emitIteratorClose(bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorClose(CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false); template MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, diff --git a/js/src/jit-test/tests/auto-regress/for-of-iterator-close-debugger.js b/js/src/jit-test/tests/auto-regress/for-of-iterator-close-debugger.js new file mode 100644 index 000000000..a4d0bf654 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/for-of-iterator-close-debugger.js @@ -0,0 +1,15 @@ +// |jit-test| error:ReferenceError + +// for-of should close iterator even if the exception is once caught by the +// debugger. + +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () { };"); +// jsfunfuzz-generated +for (var x of []) {}; +for (var l of [0]) { + for (var y = 0; y < 1; y++) { + g2; + } +} diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 161e1aa4c..3ab722b3d 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -509,16 +509,10 @@ HasLiveStackValueAtDepth(JSScript* script, jsbytecode* pc, uint32_t stackDepth) break; case JSTRY_FOR_OF: - // For-of loops have both the iterator and the result object on - // stack. The iterator is below the result object. - if (stackDepth == tn->stackDepth - 1) - return true; - break; - - case JSTRY_ITERCLOSE: - // Code that need to call IteratorClose have the iterator on the - // stack. - if (stackDepth == tn->stackDepth) + // For-of loops have the iterator, the result object, and the value + // of the result object on stack. The iterator is below the result + // object and the value. + if (stackDepth == tn->stackDepth - 2) return true; break; diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 7c3f0d120..a70356ad4 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -331,7 +331,6 @@ static void CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn) { MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || - tn->kind == JSTRY_ITERCLOSE || tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE); bool isDestructuring = tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE; @@ -442,7 +441,6 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx switch (tn->kind) { case JSTRY_FOR_IN: - case JSTRY_ITERCLOSE: case JSTRY_DESTRUCTURING_ITERCLOSE: MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER); @@ -637,19 +635,6 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen break; } - case JSTRY_ITERCLOSE: { - uint8_t* framePointer; - uint8_t* stackPointer; - BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); - Value iterValue(*reinterpret_cast(stackPointer)); - RootedObject iterObject(cx, &iterValue.toObject()); - if (!IteratorCloseForException(cx, iterObject)) { - SettleOnTryNote(cx, tn, frame, ei, rfe, pc); - return false; - } - break; - } - case JSTRY_DESTRUCTURING_ITERCLOSE: { uint8_t* framePointer; uint8_t* stackPointer; diff --git a/js/src/jsapi.h b/js/src/jsapi.h index cbef0f8fb..5523dfa1d 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -6622,5 +6622,14 @@ SetGetPerformanceGroupsCallback(JSContext*, GetGroupsCallback, void*); } /* namespace js */ +namespace js { + +enum class CompletionKind { + Normal, + Return, + Throw +}; + +} /* namespace js */ #endif /* jsapi_h */ diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 8bba3ec39..9cb7c538f 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -85,7 +85,6 @@ enum JSTryNoteKind { JSTRY_FOR_IN, JSTRY_FOR_OF, JSTRY_LOOP, - JSTRY_ITERCLOSE, JSTRY_DESTRUCTURING_ITERCLOSE }; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 6fe01de22..5d6508d59 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2599,8 +2599,6 @@ TryNoteName(JSTryNoteKind kind) return "for-of"; case JSTRY_LOOP: return "loop"; - case JSTRY_ITERCLOSE: - return "iterclose"; case JSTRY_DESTRUCTURING_ITERCLOSE: return "dstr-iterclose"; } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 7f8ff8445..eb3000e07 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1189,17 +1189,6 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) break; } - case JSTRY_ITERCLOSE: { - // The iterator object is at the top of the stack. - Value* sp = regs.spForStackDepth(tn->stackDepth); - RootedObject iterObject(cx, &sp[-1].toObject()); - if (!IteratorCloseForException(cx, iterObject)) { - SettleOnTryNote(cx, tn, ei, regs); - return ErrorReturnContinuation; - } - break; - } - case JSTRY_DESTRUCTURING_ITERCLOSE: { // Whether the destructuring iterator is done is at the top of the // stack. The iterator object is second from the top. @@ -1892,7 +1881,6 @@ CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED209) CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED211) -CASE(JSOP_UNUSED219) CASE(JSOP_UNUSED220) CASE(JSOP_UNUSED221) CASE(JSOP_UNUSED222) @@ -2637,6 +2625,15 @@ CASE(JSOP_CHECKISOBJ) } END_CASE(JSOP_CHECKISOBJ) +CASE(JSOP_CHECKISCALLABLE) +{ + if (!IsCallable(REGS.sp[-1])) { + MOZ_ALWAYS_FALSE(ThrowCheckIsCallable(cx, CheckIsCallableKind(GET_UINT8(REGS.pc)))); + goto error; + } +} +END_CASE(JSOP_CHECKISCALLABLE) + CASE(JSOP_CHECKTHIS) { if (REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) { @@ -5094,6 +5091,19 @@ js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind) return false; } +bool +js::ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind) +{ + switch (kind) { + case CheckIsCallableKind::IteratorReturn: + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE); + break; + default: + MOZ_CRASH("Unknown kind"); + } + return false; +} + bool js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame) { diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 38e23ec06..330dbef5f 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -570,6 +570,13 @@ enum class CheckIsObjectKind : uint8_t { bool ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind); +enum class CheckIsCallableKind : uint8_t { + IteratorReturn +}; + +bool +ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind); + bool ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 84f08c4d5..3848445ff 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2201,7 +2201,16 @@ */ \ macro(JSOP_HOLE, 218, "hole", NULL, 1, 0, 1, JOF_BYTE) \ \ - macro(JSOP_UNUSED219, 219,"unused219", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Checks that the top value on the stack is callable, and throws a + * TypeError if not. The operand 'kind' is used only to generate an + * appropriate error message. + * Category: Statements + * Type: Function + * Operands: uint8_t kind + * Stack: result => result, callable + */ \ + macro(JSOP_CHECKISCALLABLE, 219, "checkiscallable", NULL, 2, 1, 1, JOF_UINT8) \ macro(JSOP_UNUSED220, 220,"unused220", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED221, 221,"unused221", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED222, 222,"unused222", NULL, 1, 0, 0, JOF_BYTE) \ -- cgit v1.2.3 From 727c27a30d10a811d5a3fe04e2407cd7b3993b5e Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 17:00:47 +0200 Subject: Bug 1342553 - Part 0.2: Support JSOP_CHECKISCALLABLE in JIT Issue #74 --- js/src/jit/BaselineCompiler.cpp | 20 +++++++ js/src/jit/BaselineCompiler.h | 1 + js/src/jit/CodeGenerator.cpp | 109 ++++++++++++++++++++++-------------- js/src/jit/CodeGenerator.h | 7 +++ js/src/jit/IonBuilder.cpp | 12 ++++ js/src/jit/IonBuilder.h | 1 + js/src/jit/Lowering.cpp | 13 +++++ js/src/jit/Lowering.h | 1 + js/src/jit/MIR.h | 32 ++++++++++- js/src/jit/MOpcodes.h | 1 + js/src/jit/VMFunctions.cpp | 9 +++ js/src/jit/VMFunctions.h | 4 ++ js/src/jit/shared/LIR-shared.h | 21 +++++++ js/src/jit/shared/LOpcodes-shared.h | 1 + 14 files changed, 189 insertions(+), 43 deletions(-) (limited to 'js/src') diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 4524bae07..07d8e629d 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1381,6 +1381,26 @@ BaselineCompiler::emit_JSOP_CHECKISOBJ() return true; } +typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind); +static const VMFunction CheckIsCallableInfo = + FunctionInfo(CheckIsCallable, "CheckIsCallable"); + +bool +BaselineCompiler::emit_JSOP_CHECKISCALLABLE() +{ + frame.syncStack(0); + masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); + + prepareVMCall(); + + pushArg(Imm32(GET_UINT8(pc))); + pushArg(R0); + if (!callVM(CheckIsCallableInfo)) + return false; + + return true; +} + typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame); static const VMFunction ThrowUninitializedThisInfo = FunctionInfo(BaselineThrowUninitializedThis, diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 18e56bcd4..0bacf6f18 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -218,6 +218,7 @@ namespace jit { _(JSOP_FUNCTIONTHIS) \ _(JSOP_GLOBALTHIS) \ _(JSOP_CHECKISOBJ) \ + _(JSOP_CHECKISCALLABLE) \ _(JSOP_CHECKTHIS) \ _(JSOP_CHECKRETURN) \ _(JSOP_NEWTARGET) \ diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 3b5ec6baa..ccdc5fbfa 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -11326,25 +11326,35 @@ class OutOfLineIsCallable : public OutOfLineCodeBase } }; +template void -CodeGenerator::visitIsCallable(LIsCallable* ins) +CodeGenerator::emitIsCallableOrConstructor(Register object, Register output, Label* failure) { - Register object = ToRegister(ins->object()); - Register output = ToRegister(ins->output()); - - OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins); - addOutOfLineCode(ool, ins->mir()); - Label notFunction, hasCOps, done; masm.loadObjClass(object, output); - // Just skim proxies off. Their notion of isCallable() is more complicated. - masm.branchTestClassIsProxy(true, output, ool->entry()); + // Just skim proxies off. Their notion of isCallable()/isConstructor() is + // more complicated. + masm.branchTestClassIsProxy(true, output, failure); // An object is callable iff: // is() || (getClass()->cOps && getClass()->cOps->call). + // An object is constructor iff: + // ((is() && as().isConstructor) || + // (getClass()->cOps && getClass()->cOps->construct)). masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), ¬Function); - masm.move32(Imm32(1), output); + if (mode == Callable) { + masm.move32(Imm32(1), output); + } else { + Label notConstructor; + masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output); + masm.and32(Imm32(JSFunction::CONSTRUCTOR), output); + masm.branchTest32(Assembler::Zero, output, output, ¬Constructor); + masm.move32(Imm32(1), output); + masm.jump(&done); + masm.bind(¬Constructor); + masm.move32(Imm32(0), output); + } masm.jump(&done); masm.bind(¬Function); @@ -11355,10 +11365,26 @@ CodeGenerator::visitIsCallable(LIsCallable* ins) masm.bind(&hasCOps); masm.loadPtr(Address(output, offsetof(js::Class, cOps)), output); - masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, call)), + size_t opsOffset = mode == Callable + ? offsetof(js::ClassOps, call) + : offsetof(js::ClassOps, construct); + masm.cmpPtrSet(Assembler::NonZero, Address(output, opsOffset), ImmPtr(nullptr), output); masm.bind(&done); +} + +void +CodeGenerator::visitIsCallable(LIsCallable* ins) +{ + Register object = ToRegister(ins->object()); + Register output = ToRegister(ins->output()); + + OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins); + addOutOfLineCode(ool, ins->mir()); + + emitIsCallableOrConstructor(object, output, ool->entry()); + masm.bind(ool->rejoin()); } @@ -11378,6 +11404,36 @@ CodeGenerator::visitOutOfLineIsCallable(OutOfLineIsCallable* ool) masm.jump(ool->rejoin()); } +typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind); +static const VMFunction CheckIsCallableInfo = + FunctionInfo(CheckIsCallable, "CheckIsCallable"); + +void +CodeGenerator::visitCheckIsCallable(LCheckIsCallable* ins) +{ + ValueOperand checkValue = ToValue(ins, LCheckIsCallable::CheckValue); + Register temp = ToRegister(ins->temp()); + + // OOL code is used in the following 2 cases: + // * checkValue is not callable + // * checkValue is proxy and it's unknown whether it's callable or not + // CheckIsCallable checks if passed value is callable, regardless of the + // cases above. IsCallable operation is not observable and checking it + // again doesn't matter. + OutOfLineCode* ool = oolCallVM(CheckIsCallableInfo, ins, + ArgList(checkValue, Imm32(ins->mir()->checkKind())), + StoreNothing()); + + masm.branchTestObject(Assembler::NotEqual, checkValue, ool->entry()); + + Register object = masm.extractObject(checkValue, temp); + emitIsCallableOrConstructor(object, temp, ool->entry()); + + masm.branchTest32(Assembler::Zero, temp, temp, ool->entry()); + + masm.bind(ool->rejoin()); +} + class OutOfLineIsConstructor : public OutOfLineCodeBase { LIsConstructor* ins_; @@ -11404,37 +11460,8 @@ CodeGenerator::visitIsConstructor(LIsConstructor* ins) OutOfLineIsConstructor* ool = new(alloc()) OutOfLineIsConstructor(ins); addOutOfLineCode(ool, ins->mir()); - Label notFunction, notConstructor, hasCOps, done; - masm.loadObjClass(object, output); - - // Just skim proxies off. Their notion of isConstructor() is more complicated. - masm.branchTestClassIsProxy(true, output, ool->entry()); + emitIsCallableOrConstructor(object, output, ool->entry()); - // An object is constructor iff - // ((is() && as().isConstructor) || - // (getClass()->cOps && getClass()->cOps->construct)). - masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), ¬Function); - masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output); - masm.and32(Imm32(JSFunction::CONSTRUCTOR), output); - masm.branchTest32(Assembler::Zero, output, output, ¬Constructor); - masm.move32(Imm32(1), output); - masm.jump(&done); - masm.bind(¬Constructor); - masm.move32(Imm32(0), output); - masm.jump(&done); - - masm.bind(¬Function); - masm.branchPtr(Assembler::NonZero, Address(output, offsetof(js::Class, cOps)), - ImmPtr(nullptr), &hasCOps); - masm.move32(Imm32(0), output); - masm.jump(&done); - - masm.bind(&hasCOps); - masm.loadPtr(Address(output, offsetof(js::Class, cOps)), output); - masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, construct)), - ImmPtr(nullptr), output); - - masm.bind(&done); masm.bind(ool->rejoin()); } diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index b69e919a3..d3126651b 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -364,6 +364,12 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitCallDOMNative(LCallDOMNative* lir); void visitCallGetIntrinsicValue(LCallGetIntrinsicValue* lir); void visitCallBindVar(LCallBindVar* lir); + enum CallableOrConstructor { + Callable, + Constructor + }; + template + void emitIsCallableOrConstructor(Register object, Register output, Label* failure); void visitIsCallable(LIsCallable* lir); void visitOutOfLineIsCallable(OutOfLineIsCallable* ool); void visitIsConstructor(LIsConstructor* lir); @@ -384,6 +390,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitArrowNewTarget(LArrowNewTarget* ins); void visitCheckReturn(LCheckReturn* ins); void visitCheckIsObj(LCheckIsObj* ins); + void visitCheckIsCallable(LCheckIsCallable* ins); void visitCheckObjCoercible(LCheckObjCoercible* ins); void visitDebugCheckSelfHosted(LDebugCheckSelfHosted* ins); void visitNaNToZero(LNaNToZero* ins); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 26bba0656..ed09fb504 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -2183,6 +2183,9 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CHECKISOBJ: return jsop_checkisobj(GET_UINT8(pc)); + case JSOP_CHECKISCALLABLE: + return jsop_checkiscallable(GET_UINT8(pc)); + case JSOP_CHECKOBJCOERCIBLE: return jsop_checkobjcoercible(); @@ -10899,6 +10902,15 @@ IonBuilder::jsop_checkisobj(uint8_t kind) return true; } +bool +IonBuilder::jsop_checkiscallable(uint8_t kind) +{ + MCheckIsCallable* check = MCheckIsCallable::New(alloc(), current->pop(), kind); + current->add(check); + current->push(check); + return true; +} + bool IonBuilder::jsop_checkobjcoercible() { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index c3cd9700a..35ad120f7 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -783,6 +783,7 @@ class IonBuilder MOZ_MUST_USE bool jsop_debugger(); MOZ_MUST_USE bool jsop_newtarget(); MOZ_MUST_USE bool jsop_checkisobj(uint8_t kind); + MOZ_MUST_USE bool jsop_checkiscallable(uint8_t kind); MOZ_MUST_USE bool jsop_checkobjcoercible(); MOZ_MUST_USE bool jsop_pushcallobj(); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index a21a529be..7f28a9020 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -4688,6 +4688,19 @@ LIRGenerator::visitCheckIsObj(MCheckIsObj* ins) assignSafepoint(lir, ins); } +void +LIRGenerator::visitCheckIsCallable(MCheckIsCallable* ins) +{ + MDefinition* checkVal = ins->checkValue(); + MOZ_ASSERT(checkVal->type() == MIRType::Value); + + LCheckIsCallable* lir = new(alloc()) LCheckIsCallable(useBox(checkVal), + temp()); + redefine(ins, checkVal); + add(lir, ins); + assignSafepoint(lir, ins); +} + void LIRGenerator::visitCheckObjCoercible(MCheckObjCoercible* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 4062c0960..b2805cb7a 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -329,6 +329,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitGuardSharedTypedArray(MGuardSharedTypedArray* ins); void visitCheckReturn(MCheckReturn* ins); void visitCheckIsObj(MCheckIsObj* ins); + void visitCheckIsCallable(MCheckIsCallable* ins); void visitCheckObjCoercible(MCheckObjCoercible* ins); void visitDebugCheckSelfHosted(MDebugCheckSelfHosted* ins); }; diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 3caa7e357..2de91e2df 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -13455,8 +13455,9 @@ class MCheckIsObj { uint8_t checkKind_; - explicit MCheckIsObj(MDefinition* toCheck, uint8_t checkKind) - : MUnaryInstruction(toCheck), checkKind_(checkKind) + MCheckIsObj(MDefinition* toCheck, uint8_t checkKind) + : MUnaryInstruction(toCheck), + checkKind_(checkKind) { setResultType(MIRType::Value); setResultTypeSet(toCheck->resultTypeSet()); @@ -13475,6 +13476,33 @@ class MCheckIsObj } }; +class MCheckIsCallable + : public MUnaryInstruction, + public BoxInputsPolicy::Data +{ + uint8_t checkKind_; + + MCheckIsCallable(MDefinition* toCheck, uint8_t checkKind) + : MUnaryInstruction(toCheck), + checkKind_(checkKind) + { + setResultType(MIRType::Value); + setResultTypeSet(toCheck->resultTypeSet()); + setGuard(); + } + + public: + INSTRUCTION_HEADER(CheckIsCallable) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, checkValue)) + + uint8_t checkKind() const { return checkKind_; } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } +}; + class MCheckObjCoercible : public MUnaryInstruction, public BoxInputsPolicy::Data diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 1a6911247..bb2ab8190 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -285,6 +285,7 @@ namespace jit { _(ArrowNewTarget) \ _(CheckReturn) \ _(CheckIsObj) \ + _(CheckIsCallable) \ _(CheckObjCoercible) \ _(DebugCheckSelfHosted) \ _(AsmJSNeg) \ diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 4edbc3c83..77b9e3647 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1349,5 +1349,14 @@ BaselineGetFunctionThis(JSContext* cx, BaselineFrame* frame, MutableHandleValue return GetFunctionThis(cx, frame, res); } +bool +CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind) +{ + if (!IsCallable(v)) + return ThrowCheckIsCallable(cx, kind); + + return true; +} + } // namespace jit } // namespace js diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index f754d58c7..572f05373 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -13,6 +13,7 @@ #include "jit/CompileInfo.h" #include "jit/JitFrames.h" +#include "vm/Interpreter.h" namespace js { @@ -802,6 +803,9 @@ ThrowObjectCoercible(JSContext* cx, HandleValue v); MOZ_MUST_USE bool BaselineGetFunctionThis(JSContext* cx, BaselineFrame* frame, MutableHandleValue res); +MOZ_MUST_USE bool +CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind); + } // namespace jit } // namespace js diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index f8e0ce9cc..9dcb527c5 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -8893,6 +8893,27 @@ class LCheckIsObj : public LInstructionHelper } }; +class LCheckIsCallable : public LInstructionHelper +{ + public: + LIR_HEADER(CheckIsCallable) + + static const size_t CheckValue = 0; + + LCheckIsCallable(const LBoxAllocation& value, const LDefinition& temp) { + setBoxOperand(CheckValue, value); + setTemp(0, temp); + } + + const LDefinition* temp() { + return getTemp(0); + } + + MCheckIsCallable* mir() const { + return mir_->toCheckIsCallable(); + } +}; + class LCheckObjCoercible : public LCallInstructionHelper { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index e57751437..3eea1b449 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -402,6 +402,7 @@ _(ArrowNewTarget) \ _(CheckReturn) \ _(CheckIsObj) \ + _(CheckIsCallable) \ _(CheckObjCoercible) \ _(DebugCheckSelfHosted) \ _(AsmJSLoadHeap) \ -- cgit v1.2.3 From 05441d12b6bbc9dde268914fcfd374db61b83462 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 18:21:15 +0200 Subject: Bug 1346862 - Fix IteratorClose due to non-local jumps being catchable by try statements inside for-of Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 20 +++++------- js/src/frontend/BytecodeEmitter.h | 2 -- js/src/jit/JitFrames.cpp | 31 ++++++++++++++++++- js/src/jsscript.h | 1 + js/src/shell/js.cpp | 6 ++-- .../Statements/for-of-iterator-close-throw.js | 35 +++++++++++++++++++++ js/src/vm/Interpreter.cpp | 36 ++++++++++++++++++++++ 7 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 js/src/tests/ecma_6/Statements/for-of-iterator-close-throw.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4d6ff6305..b2e48d7ea 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2065,7 +2065,11 @@ class ForOfLoopControl : public LoopControl bool emitIteratorClose(BytecodeEmitter* bce, CompletionKind completionKind = CompletionKind::Normal) { - return bce->emitIteratorClose(completionKind, allowSelfHosted_); + ptrdiff_t start = bce->offset(); + if (!bce->emitIteratorClose(completionKind, allowSelfHosted_)) + return false; + ptrdiff_t end = bce->offset(); + return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end); } bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) { @@ -2614,17 +2618,6 @@ BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) return true; } -bool -BytecodeEmitter::flushPops(int* npops) -{ - MOZ_ASSERT(*npops != 0); - if (!emitUint16Operand(JSOP_POPN, *npops)) - return false; - - *npops = 0; - return true; -} - namespace { class NonLocalExitControl @@ -2718,8 +2711,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue; auto flushPops = [&npops](BytecodeEmitter* bce) { - if (npops && !bce->flushPops(&npops)) + if (npops && !bce->emitUint16Operand(JSOP_POPN, npops)) return false; + npops = 0; return true; }; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 7ff40b462..7ac9e540b 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -452,8 +452,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter JSOp strictifySetNameOp(JSOp op); - MOZ_MUST_USE bool flushPops(int* npops); - MOZ_MUST_USE bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset); // Emit one bytecode. diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index a70356ad4..966d952d3 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -436,6 +436,8 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx if (!script->hasTrynotes()) return; + bool inForOfIterClose = false; + for (TryNoteIterIon tni(cx, frame); !tni.done(); ++tni) { JSTryNote* tn = *tni; @@ -447,12 +449,23 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx CloseLiveIteratorIon(cx, frame, tn); break; + case JSTRY_FOR_OF_ITERCLOSE: + inForOfIterClose = true; + break; + case JSTRY_FOR_OF: + inForOfIterClose = false; + break; + case JSTRY_LOOP: break; case JSTRY_CATCH: if (cx->isExceptionPending()) { + // See corresponding comment in ProcessTryNotes. + if (inForOfIterClose) + break; + // Ion can compile try-catch, but bailing out to catch // exceptions is slow. Reset the warm-up counter so that if we // catch many exceptions we won't Ion-compile the script. @@ -583,6 +596,7 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen ResumeFromException* rfe, jsbytecode** pc) { RootedScript script(cx, frame.baselineFrame()->script()); + bool inForOfIterClose = false; for (TryNoteIterBaseline tni(cx, frame.baselineFrame(), *pc); !tni.done(); ++tni) { JSTryNote* tn = *tni; @@ -593,7 +607,11 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen // If we're closing a legacy generator, we have to skip catch // blocks. if (cx->isClosingGenerator()) - continue; + break; + + // See corresponding comment in ProcessTryNotes. + if (inForOfIterClose) + break; SettleOnTryNote(cx, tn, frame, ei, rfe, pc); @@ -609,6 +627,10 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen } case JSTRY_FINALLY: { + // See corresponding comment in ProcessTryNotes. + if (inForOfIterClose) + break; + SettleOnTryNote(cx, tn, frame, ei, rfe, pc); rfe->kind = ResumeFromException::RESUME_FINALLY; rfe->target = script->baselineScript()->nativeCodeForPC(script, *pc); @@ -652,7 +674,14 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen break; } + case JSTRY_FOR_OF_ITERCLOSE: + inForOfIterClose = true; + break; + case JSTRY_FOR_OF: + inForOfIterClose = false; + break; + case JSTRY_LOOP: break; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 9cb7c538f..87da79901 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -85,6 +85,7 @@ enum JSTryNoteKind { JSTRY_FOR_IN, JSTRY_FOR_OF, JSTRY_LOOP, + JSTRY_FOR_OF_ITERCLOSE, JSTRY_DESTRUCTURING_ITERCLOSE }; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 5d6508d59..b53914942 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2599,6 +2599,8 @@ TryNoteName(JSTryNoteKind kind) return "for-of"; case JSTRY_LOOP: return "loop"; + case JSTRY_FOR_OF_ITERCLOSE: + return "for-of-iterclose"; case JSTRY_DESTRUCTURING_ITERCLOSE: return "dstr-iterclose"; } @@ -2612,14 +2614,14 @@ TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) if (!script->hasTrynotes()) return true; - if (sp->put("\nException table:\nkind stack start end\n") < 0) + if (sp->put("\nException table:\nkind stack start end\n") < 0) return false; JSTryNote* tn = script->trynotes()->vector; JSTryNote* tnlimit = tn + script->trynotes()->length; do { uint32_t startOff = script->pcToOffset(script->main()) + tn->start; - if (!sp->jsprintf(" %-14s %6u %8u %8u\n", + if (!sp->jsprintf(" %-16s %6u %8u %8u\n", TryNoteName(static_cast(tn->kind)), tn->stackDepth, startOff, startOff + tn->length)) { diff --git a/js/src/tests/ecma_6/Statements/for-of-iterator-close-throw.js b/js/src/tests/ecma_6/Statements/for-of-iterator-close-throw.js new file mode 100644 index 000000000..1974e416b --- /dev/null +++ b/js/src/tests/ecma_6/Statements/for-of-iterator-close-throw.js @@ -0,0 +1,35 @@ +function test() { + var returnCalled = 0; + var returnCalledExpected = 0; + var catchEntered = 0; + var finallyEntered = 0; + var finallyEnteredExpected = 0; + var iterable = {}; + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + throw 42; + } + }); + + // inner try cannot catch IteratorClose throwing + assertThrowsValue(function() { + for (var x of iterable) { + try { + return; + } catch (e) { + catchEntered++; + } finally { + finallyEntered++; + } + } + }, 42); + assertEq(returnCalled, ++returnCalledExpected); + assertEq(catchEntered, 0); + assertEq(finallyEntered, ++finallyEnteredExpected); +} + +test(); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index eb3000e07..d20e5284d 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1156,6 +1156,7 @@ enum HandleErrorContinuation static HandleErrorContinuation ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) { + bool inForOfIterClose = false; for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) { JSTryNote* tn = *tni; @@ -1164,10 +1165,38 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) /* Catch cannot intercept the closing of a generator. */ if (cx->isClosingGenerator()) break; + + // If IteratorClose due to abnormal completion threw inside a + // for-of loop, it is not catchable by try statements inside of + // the for-of loop. + // + // This is handled by this weirdness in the exception handler + // instead of in bytecode because it is hard to do so in bytecode: + // + // 1. IteratorClose emitted due to abnormal completion (break, + // throw, return) are emitted inline, at the source location of + // the break, throw, or return statement. For example: + // + // for (x of iter) { + // try { return; } catch (e) { } + // } + // + // From the try-note nesting's perspective, the IteratorClose + // resulting from |return| is covered by the inner try, when it + // should not be. + // + // 2. Try-catch notes cannot be disjoint. That is, we can't have + // multiple notes with disjoint pc ranges jumping to the same + // catch block. + if (inForOfIterClose) + break; SettleOnTryNote(cx, tn, ei, regs); return CatchContinuation; case JSTRY_FINALLY: + // See note above. + if (inForOfIterClose) + break; SettleOnTryNote(cx, tn, ei, regs); return FinallyContinuation; @@ -1206,7 +1235,14 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) break; } + case JSTRY_FOR_OF_ITERCLOSE: + inForOfIterClose = true; + break; + case JSTRY_FOR_OF: + inForOfIterClose = false; + break; + case JSTRY_LOOP: break; -- cgit v1.2.3 From aafdd314442c903815f6fdf6072b001c25ae85c5 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 18:24:06 +0200 Subject: Bug 1357075 - Pad a nop to unwind to the scope just before a destructuring iterator close trynote Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 8 ++++++++ js/src/jit-test/tests/parser/bug-1357075.js | 10 ++++++++++ js/src/jit/BaselineCompiler.cpp | 6 ++++++ js/src/jit/BaselineCompiler.h | 7 ++++--- js/src/jit/IonBuilder.cpp | 1 + js/src/vm/Interpreter.cpp | 5 ++++- js/src/vm/Opcodes.h | 11 ++++++++++- 7 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 js/src/jit-test/tests/parser/bug-1357075.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index b2e48d7ea..4d3b60c2f 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -5318,6 +5318,14 @@ BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, In { MOZ_ASSERT(this->stackDepth >= iterDepth); + // Pad a nop at the beginning of the bytecode covered by the trynote so + // that when unwinding environments, we may unwind to the scope + // corresponding to the pc *before* the start, in case the first bytecode + // emitted by |emitter| is the start of an inner scope. See comment above + // UnwindEnvironmentToTryPc. + if (!emit1(JSOP_TRY_DESTRUCTURING_ITERCLOSE)) + return false; + ptrdiff_t start = offset(); if (!emitter(this)) return false; diff --git a/js/src/jit-test/tests/parser/bug-1357075.js b/js/src/jit-test/tests/parser/bug-1357075.js new file mode 100644 index 000000000..47482e372 --- /dev/null +++ b/js/src/jit-test/tests/parser/bug-1357075.js @@ -0,0 +1,10 @@ +// |jit-test| error: TypeError + +var iterable = {}; +var iterator = { + return: 1 +}; +iterable[Symbol.iterator] = function() { + return iterator; +}; +for ([ class get {} ().iterator ] of [iterable]) {} diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 07d8e629d..3fa5a80ed 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1062,6 +1062,12 @@ BaselineCompiler::emit_JSOP_NOP_DESTRUCTURING() return true; } +bool +BaselineCompiler::emit_JSOP_TRY_DESTRUCTURING_ITERCLOSE() +{ + return true; +} + bool BaselineCompiler::emit_JSOP_LABEL() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 0bacf6f18..6b5bf009e 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -226,7 +226,7 @@ namespace jit { _(JSOP_SPREADSUPERCALL) \ _(JSOP_THROWSETCONST) \ _(JSOP_THROWSETALIASEDCONST) \ - _(JSOP_THROWSETCALLEE) \ + _(JSOP_THROWSETCALLEE) \ _(JSOP_INITHIDDENPROP_GETTER) \ _(JSOP_INITHIDDENPROP_SETTER) \ _(JSOP_INITHIDDENELEM) \ @@ -234,8 +234,9 @@ namespace jit { _(JSOP_INITHIDDENELEM_SETTER) \ _(JSOP_CHECKOBJCOERCIBLE) \ _(JSOP_DEBUGCHECKSELFHOSTED) \ - _(JSOP_JUMPTARGET) \ - _(JSOP_IS_CONSTRUCTING) + _(JSOP_JUMPTARGET) \ + _(JSOP_IS_CONSTRUCTING) \ + _(JSOP_TRY_DESTRUCTURING_ITERCLOSE) class BaselineCompiler : public BaselineCompilerSpecific { diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index ed09fb504..54d05cac4 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1678,6 +1678,7 @@ IonBuilder::inspectOpcode(JSOp op) switch (op) { case JSOP_NOP: case JSOP_NOP_DESTRUCTURING: + case JSOP_TRY_DESTRUCTURING_ITERCLOSE: case JSOP_LINENO: case JSOP_LOOPENTRY: case JSOP_JUMPTARGET: diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index d20e5284d..b747e4d7a 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1080,6 +1080,9 @@ js::UnwindEnvironmentToTryPc(JSScript* script, JSTryNote* tn) if (tn->kind == JSTRY_CATCH || tn->kind == JSTRY_FINALLY) { pc -= JSOP_TRY_LENGTH; MOZ_ASSERT(*pc == JSOP_TRY); + } else if (tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE) { + pc -= JSOP_TRY_DESTRUCTURING_ITERCLOSE_LENGTH; + MOZ_ASSERT(*pc == JSOP_TRY_DESTRUCTURING_ITERCLOSE); } return pc; } @@ -1917,7 +1920,7 @@ CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED209) CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED211) -CASE(JSOP_UNUSED220) +CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE) CASE(JSOP_UNUSED221) CASE(JSOP_UNUSED222) CASE(JSOP_UNUSED223) diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 3848445ff..4b044c8d8 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2211,7 +2211,16 @@ * Stack: result => result, callable */ \ macro(JSOP_CHECKISCALLABLE, 219, "checkiscallable", NULL, 2, 1, 1, JOF_UINT8) \ - macro(JSOP_UNUSED220, 220,"unused220", NULL, 1, 0, 0, JOF_BYTE) \ + \ + /* + * No-op used by the exception unwinder to determine the correct + * environment to unwind to when performing IteratorClose due to + * destructuring. + * Category: Other + * Operands: + * Stack: => + */ \ + macro(JSOP_TRY_DESTRUCTURING_ITERCLOSE, 220, "try-destructuring-iterclose", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED221, 221,"unused221", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED222, 222,"unused222", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED223, 223,"unused223", NULL, 1, 0, 0, JOF_BYTE) \ -- cgit v1.2.3 From 70c8cf8db71880c1ab1f8fee4787a19316960dac Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 19:06:08 +0200 Subject: Bug 1360839 - Call IteratorClose due to abrupt completion from yield Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 70 +++++++++++++++++++--- js/src/frontend/BytecodeEmitter.h | 4 +- .../ecma_6/Generators/yield-iterator-close.js | 58 ++++++++++++++++++ 3 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 js/src/tests/ecma_6/Generators/yield-iterator-close.js (limited to 'js/src') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4d3b60c2f..c7c615ccf 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1534,13 +1534,19 @@ class MOZ_STACK_CLASS TryEmitter // stream and fixed-up later. // // If ShouldUseControl is DontUseControl, all that handling is skipped. - // DontUseControl is used by yield*, that matches to the following: - // * has only one catch block - // * has no catch guard - // * has JSOP_GOTO at the end of catch-block - // * has no non-local-jump - // * doesn't use finally block for normal completion of try-block and + // DontUseControl is used by yield* and the internal try-catch around + // IteratorClose. These internal uses must: + // * have only one catch block + // * have no catch guard + // * have JSOP_GOTO at the end of catch-block + // * have no non-local-jump + // * don't use finally block for normal completion of try-block and // catch-block + // + // Additionally, a finally block may be emitted when ShouldUseControl is + // DontUseControl, even if the kind is not TryCatchFinally or TryFinally, + // because GOSUBs are not emitted. This internal use shares the + // requirements as above. Maybe controlInfo_; int depth_; @@ -1708,7 +1714,17 @@ class MOZ_STACK_CLASS TryEmitter public: bool emitFinally(Maybe finallyPos = Nothing()) { - MOZ_ASSERT(hasFinally()); + // If we are using controlInfo_ (i.e., emitting a syntactic try + // blocks), we must have specified up front if there will be a finally + // close. For internal try blocks, like those emitted for yield* and + // IteratorClose inside for-of loops, we can emitFinally even without + // specifying up front, since the internal try blocks emit no GOSUBs. + if (!controlInfo_) { + if (kind_ == TryCatch) + kind_ = TryCatchFinally; + } else { + MOZ_ASSERT(hasFinally()); + } if (state_ == Try) { if (!emitTryEnd()) @@ -2004,21 +2020,31 @@ class ForOfLoopControl : public LoopControl // } Maybe tryCatch_; + // Used to track if any yields were emitted between calls to to + // emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose. + uint32_t numYieldsAtBeginCodeNeedingIterClose_; + bool allowSelfHosted_; public: ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted) : LoopControl(bce, StatementKind::ForOfLoop), iterDepth_(iterDepth), + numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), allowSelfHosted_(allowSelfHosted) { } bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { - tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal); + tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); if (!tryCatch_->emitTry()) return false; + + MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX); + numYieldsAtBeginCodeNeedingIterClose_ = bce->yieldOffsetList.numYields; + return true; } @@ -2056,10 +2082,33 @@ class ForOfLoopControl : public LoopControl if (!bce->emit1(JSOP_THROW)) // ITER ... return false; + // If any yields were emitted, then this for-of loop is inside a star + // generator and must handle the case of Generator.return. Like in + // yield*, it is handled with a finally block. + uint32_t numYieldsEmitted = bce->yieldOffsetList.numYields; + if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) { + if (!tryCatch_->emitFinally()) + return false; + + IfThenElseEmitter ifGeneratorClosing(bce); + if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING + return false; + if (!ifGeneratorClosing.emitIf()) // ITER ... FTYPE FVALUE + return false; + if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER + return false; + if (!emitIteratorClose(bce, CompletionKind::Normal)) // ITER ... FTYPE FVALUE + return false; + if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE + return false; + } + if (!tryCatch_->emitEnd()) return false; tryCatch_.reset(); + numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX; + return true; } @@ -4748,6 +4797,11 @@ BytecodeEmitter::emitYieldOp(JSOp op) return false; } + if (op == JSOP_YIELD) + yieldOffsetList.numYields++; + else + yieldOffsetList.numAwaits++; + SET_UINT24(code(off), yieldIndex); if (!yieldOffsetList.append(offset())) diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 7ac9e540b..04307c8c1 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -100,7 +100,9 @@ struct CGScopeNoteList { struct CGYieldOffsetList { Vector list; - explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx) {} + uint32_t numYields; + uint32_t numAwaits; + explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx), numYields(0), numAwaits(0) {} MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); } size_t length() const { return list.length(); } diff --git a/js/src/tests/ecma_6/Generators/yield-iterator-close.js b/js/src/tests/ecma_6/Generators/yield-iterator-close.js new file mode 100644 index 000000000..970ad494d --- /dev/null +++ b/js/src/tests/ecma_6/Generators/yield-iterator-close.js @@ -0,0 +1,58 @@ +// Test that IteratorClose is called when a Generator is abruptly completed by +// Generator.return. + +var returnCalled = 0; +function* wrapNoThrow() { + let iter = { + [Symbol.iterator]() { + return this; + }, + next() { + return { value: 10, done: false }; + }, + return() { + returnCalled++; + return {}; + } + }; + for (const i of iter) { + yield i; + } +} + +// Breaking calls Generator.return, which causes the yield above to resume with +// an abrupt completion of kind "return", which then calls +// iter.return. +for (const i of wrapNoThrow()) { + break; +} +assertEq(returnCalled, 1); + +function* wrapThrow() { + let iter = { + [Symbol.iterator]() { + return this; + }, + next() { + return { value: 10, done: false }; + }, + return() { + throw 42; + } + }; + for (const i of iter) { + yield i; + } +} + +// Breaking calls Generator.return, which, like above, calls iter.return. If +// iter.return throws, since the yield is resuming with an abrupt completion of +// kind "return", the newly thrown value takes precedence over returning. +assertThrowsValue(() => { + for (const i of wrapThrow()) { + break; + } +}, 42); + +if (typeof reportCompare === "function") + reportCompare(0, 0); -- cgit v1.2.3