Skip to content

Commit 1c207c9

Browse files
authored
Fix empty search match before surrogate pair causing infinite loop (#5389)
* Fix empty search match causing infinite loop * Improve regex u flag feature detection * Use RegExp.unicode instead of $supportsUnicodeFlag * Fix mistake in last commit * Fix failing tests * Add new unicode mode tests * Add missing semi colons
1 parent e06c193 commit 1c207c9

File tree

4 files changed

+47
-23
lines changed

4 files changed

+47
-23
lines changed

src/ext/searchbox.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ class SearchBox {
167167
updateCounter() {
168168
var editor = this.editor;
169169
var regex = editor.$search.$options.re;
170+
var supportsUnicodeFlag = regex.unicode;
170171
var all = 0;
171172
var before = 0;
172173
if (regex) {
@@ -188,7 +189,7 @@ class SearchBox {
188189
if (all > MAX_COUNT)
189190
break;
190191
if (!m[0]) {
191-
regex.lastIndex = last += 1;
192+
regex.lastIndex = last += lang.skipEmptyMatch(value, last, supportsUnicodeFlag);
192193
if (last >= value.length)
193194
break;
194195
}

src/lib/lang.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,6 @@ exports.supportsLookbehind = function () {
181181
return true;
182182
};
183183

184-
exports.supportsUnicodeFlag = function () {
185-
try {
186-
new RegExp('^.$', 'u');
187-
} catch (error) {
188-
return false;
189-
}
190-
return true;
184+
exports.skipEmptyMatch = function(line, last, supportsUnicodeFlag) {
185+
return supportsUnicodeFlag && line.codePointAt(last) > 0xffff ? 2 : 1;
191186
};

src/search.js

+10-15
Original file line numberDiff line numberDiff line change
@@ -218,28 +218,22 @@ class Search {
218218

219219
if (!options.needle)
220220
return options.re = false;
221-
222-
if (options.$supportsUnicodeFlag === undefined) {
223-
options.$supportsUnicodeFlag = lang.supportsUnicodeFlag();
224-
}
221+
222+
if (!options.regExp)
223+
needle = lang.escapeRegExp(needle);
224+
225+
var modifier = options.caseSensitive ? "gm" : "gmi";
225226

226227
try {
227228
new RegExp(needle, "u");
229+
options.$supportsUnicodeFlag = true;
230+
modifier += "u";
228231
} catch (e) {
229232
options.$supportsUnicodeFlag = false; //left for backward compatibility with previous versions for cases like /ab\{2}/gu
230233
}
231-
232-
if (!options.regExp)
233-
needle = lang.escapeRegExp(needle);
234234

235235
if (options.wholeWord)
236236
needle = addWordBoundary(needle, options);
237-
238-
var modifier = options.caseSensitive ? "gm" : "gmi";
239-
240-
if (options.$supportsUnicodeFlag) {
241-
modifier += "u";
242-
}
243237

244238
options.$isMultiLine = !$disableFakeMultiline && /[\n\r]/.test(needle);
245239
if (options.$isMultiLine)
@@ -270,6 +264,7 @@ class Search {
270264
return false;
271265
var backwards = options.backwards == true;
272266
var skipCurrent = options.skipCurrent != false;
267+
var supportsUnicodeFlag = re.unicode;
273268

274269
var range = options.range;
275270
var start = options.start;
@@ -343,7 +338,7 @@ class Search {
343338
last = m.index;
344339
if (!length) {
345340
if (last >= line.length) break;
346-
re.lastIndex = last += 1;
341+
re.lastIndex = last += lang.skipEmptyMatch(line, last, supportsUnicodeFlag);
347342
}
348343
if (m.index + length > endIndex)
349344
break;
@@ -369,7 +364,7 @@ class Search {
369364
if (callback(row, last, row,last + length))
370365
return true;
371366
if (!length) {
372-
re.lastIndex = last += 1;
367+
re.lastIndex = last += lang.skipEmptyMatch(line, last, supportsUnicodeFlag);
373368
if (last >= line.length) return false;
374369
}
375370
}

src/search_test.js

+33
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var MockRenderer = require("./test/mockrenderer").MockRenderer;
99
var Editor = require("./editor").Editor;
1010
var Search = require("./search").Search;
1111
var assert = require("./test/assertions");
12+
var Range = require("./range").Range;
1213

1314
module.exports = {
1415
"test: configure the search object" : function() {
@@ -172,6 +173,38 @@ module.exports = {
172173
assert.position(range.end, 1, 6);
173174
},
174175

176+
"test: return to unicode mode when possible": function() {
177+
var session = new EditSession(["𝓕oo"]);
178+
179+
var search = new Search().set({
180+
needle: "}",
181+
regExp: true
182+
});
183+
184+
search.find(session);
185+
search.set({
186+
needle: "."
187+
});
188+
189+
var range = search.find(session);
190+
assert.position(range.start, 0, 0);
191+
assert.position(range.end, 0, 2);
192+
},
193+
194+
"test: empty match before surrogate pair": function() {
195+
var session = new EditSession(["𝓕oo"]);
196+
197+
var search = new Search().set({
198+
needle: "()",
199+
regExp: true,
200+
start: new Range(0, 0, 0, 0)
201+
});
202+
203+
var range = search.find(session);
204+
assert.position(range.start, 0, 2);
205+
assert.position(range.end, 0, 2);
206+
},
207+
175208
"test: find backwards": function() {
176209
var session = new EditSession(["juhu juhu juhu juhu"]);
177210
session.getSelection().moveCursorTo(0, 10);

0 commit comments

Comments
 (0)