View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.regexp;
16  
17  import static java.nio.charset.StandardCharsets.ISO_8859_1;
18  
19  import org.apache.commons.io.IOUtils;
20  import org.htmlunit.WebDriverTestCase;
21  import org.htmlunit.junit.BrowserRunner;
22  import org.htmlunit.junit.annotation.Alerts;
23  import org.junit.Test;
24  import org.junit.runner.RunWith;
25  
26  /**
27   * Tests for {@link HtmlUnitRegExpProxy}.
28   *
29   * @author Marc Guillemot
30   * @author Ahmed Ashour
31   * @author Frank Danek
32   * @author Ronald Brill
33   * @author Carsten Steurl
34   * @author Leszek Hoppe
35   * @author Atsushi Nakagawa
36   * @author Lai Quang Duong
37   */
38  @RunWith(BrowserRunner.class)
39  public class RegExp3Test extends WebDriverTestCase {
40  
41      private static final String SCRIPT_TEST_MATCH = "function arrayToString(_arr) {\n"
42          + "  if (_arr == null) return null;\n"
43          + "  var s = '[';\n"
44          + "  for (var i = 0; i < _arr.length; i++)\n"
45          + "  {\n"
46          + "    if (i != 0) s += ', ';\n"
47          + "    s += _arr[i];\n"
48          + "  }\n"
49          + "  s += ']';\n"
50          + "  return s;\n"
51          + "}\n"
52  
53          + "function assertArrEquals(actual, expected) {\n"
54          + "  if (expected == null) {\n"
55          + "    if (actual != null)\n"
56          + "      throw 'Expected >null< got >' + actual + '<';\n"
57          + "    else return;\n"
58          + "  }\n"
59          + "  var expectedStr = arrayToString(expected);\n"
60          + "  var actualStr = arrayToString(actual);\n"
61          + "  if (expectedStr != actualStr)\n"
62          + "    throw 'Expected >' + expectedStr + '< got >' + actualStr + '<';\n"
63          + "}\n"
64  
65          + "assertArrEquals('ab'.match(), ['']);\n"
66          + "assertArrEquals('ab'.match('foo'), null);\n"
67          + "assertArrEquals('ab'.match('a'), ['a']);\n"
68          + "assertArrEquals('abab'.match('a'), ['a']);\n"
69          + "assertArrEquals('abab'.match('.a'), ['ba']);\n"
70          + "assertArrEquals('abab'.match(/.a/), ['ba']);\n"
71          + "assertArrEquals('li'.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i), ['li', undefined, 'li', '']);\n"
72          + "assertArrEquals('abab'.match(new RegExp('.a')), ['ba']);\n"
73          + "var s = '<script>var a = 1;</' + 'script>';\n"
74          + "var re = '(?:<script.*?>)((\\n|\\r|.)*?)(?:<\\/script>)';\n"
75          + "assertArrEquals(s.match(re), [s, 'var a = 1;', ';']);\n";
76  
77      /**
78       * @throws Exception if the test fails
79       */
80      @Test
81      @Alerts("ab21")
82      public void replaceNormalStringReplace() throws Exception {
83          testEvaluate("'121'.replace('1', 'ab')");
84      }
85  
86      /**
87       * @throws Exception if the test fails
88       */
89      @Test
90      @Alerts("xyz121")
91      public void replaceNormalStringEmptySearch() throws Exception {
92          testEvaluate("'121'.replace('', 'xyz')");
93      }
94  
95      /**
96       * @throws Exception if the test fails
97       */
98      @Test
99      @Alerts("1")
100     public void replaceNormalStringEmptyReplace() throws Exception {
101         testEvaluate("'121'.replace('21', '')");
102     }
103 
104     /**
105      * @throws Exception if the test fails
106      */
107     @Test
108     @Alerts("xyz")
109     public void replaceNormalStringEmptyStringEmptySearch() throws Exception {
110         testEvaluate("''.replace('', 'xyz')");
111     }
112 
113     /**
114      * @throws Exception if the test fails
115      */
116     @Test
117     @Alerts("a$c21")
118     public void replaceNormalStringReplaceSingleDollar() throws Exception {
119         testEvaluate("'121'.replace('1', 'a$c')");
120     }
121 
122     /**
123      * @throws Exception if the test fails
124      */
125     @Test
126     @Alerts("a$c21")
127     public void replaceNormalStringReplaceDoubleDollar() throws Exception {
128         testEvaluate("'121'.replace('1', 'a$$c')");
129     }
130 
131     /**
132      * @throws Exception if the test fails
133      */
134     @Test
135     @Alerts("a121")
136     public void replaceNormalStringReplaceSubstring() throws Exception {
137         testEvaluate("'121'.replace('1', 'a$&')");
138     }
139 
140     /**
141      * @throws Exception if the test fails
142      */
143     @Test
144     @Alerts("abaabe")
145     public void replaceNormalStringReplacePreceeding() throws Exception {
146         testEvaluate("'abcde'.replace('cd', 'a$`')");
147     }
148 
149     /**
150      * @throws Exception if the test fails
151      */
152     @Test
153     @Alerts("a21")
154     public void replaceNormalStringReplacePreceedingEmpty() throws Exception {
155         testEvaluate("'121'.replace('1', 'a$`')");
156     }
157 
158     /**
159      * @throws Exception if the test fails
160      */
161     @Test
162     @Alerts("abaee")
163     public void replaceNormalStringReplaceFollowing() throws Exception {
164         testEvaluate("'abcde'.replace('cd', \"a$'\")");
165     }
166 
167     /**
168      * @throws Exception if the test fails
169      */
170     @Test
171     @Alerts("aba")
172     public void replaceNormalStringReplaceFollowingEmpty() throws Exception {
173         testEvaluate("'abcd'.replace('cd', \"a$'\")");
174     }
175 
176     /**
177      * @throws Exception if the test fails
178      */
179     @Test
180     @Alerts("aba$0")
181     public void replaceNormalStringReplaceGroupZero() throws Exception {
182         testEvaluate("'abcd'.replace('cd', 'a$0')");
183     }
184 
185     /**
186      * @throws Exception if the test fails
187      */
188     @Test
189     @Alerts("aba$1")
190     public void replaceNormalStringReplaceGroupOne() throws Exception {
191         testEvaluate("'abcd'.replace('cd', 'a$1')");
192     }
193 
194     /**
195      * Test for bug http://sourceforge.net/p/htmlunit/bugs/513/.
196      * @throws Exception if the test fails
197      */
198     @Test
199     @Alerts("123456")
200     public void replaceNormalStringWithRegexpChars() throws Exception {
201         testEvaluate("'123456'.replace('/{\\d+}', '')");
202     }
203 
204     /**
205      * @throws Exception if the test fails
206      */
207     @Test
208     @Alerts("123456")
209     public void replaceWithUndefinedPattern() throws Exception {
210         final String html = "<html><head><script>\n"
211             + LOG_TITLE_FUNCTION
212             + "  function test() {\n"
213             + "    var pattern;\n"
214             + "    log('123456'.replace(pattern, ''));\n"
215             + "  }\n"
216             + "</script></head><body onload='test()'>\n"
217             + "</body></html>";
218 
219         loadPageVerifyTitle2(html);
220     }
221 
222     /**
223      * @throws Exception if the test fails
224      */
225     @Test
226     @Alerts("123456")
227     public void replace() throws Exception {
228         final String html = "<html><head><script>\n"
229             + LOG_TITLE_FUNCTION
230             + "  function test() {\n"
231             + "    var pattern = /{\\d+}/g;\n"
232             + "    log('123456'.replace(pattern, ''));\n"
233             + "  }\n"
234             + "</script></head><body onload='test()'>\n"
235             + "</body></html>";
236 
237         loadPageVerifyTitle2(html);
238     }
239 
240     /**
241      * @throws Exception if the test fails
242      */
243     @Test
244     public void match() throws Exception {
245         final String html = "<html><head><script>\n"
246             + LOG_TITLE_FUNCTION
247             + SCRIPT_TEST_MATCH
248             + "</script></head><body>\n"
249             + "</body></html>";
250 
251         loadPageVerifyTitle2(html);
252     }
253 
254     /**
255      * @throws Exception if the test fails
256      */
257     @Test
258     @Alerts("0")
259     public void index() throws Exception {
260         final String html = "<html><head><script>\n"
261             + LOG_TITLE_FUNCTION
262             + "  function test() {\n"
263             + "    var match = '#{tests} tests'.match(/(^|.|\\r|\\n)(#\\{(.*?)\\})/);\n"
264             + "    log(match.index);\n"
265             + "  }\n"
266             + "</script></head><body onload='test()'>\n"
267             + "</body></html>";
268 
269         loadPageVerifyTitle2(html);
270     }
271 
272     /**
273      * @throws Exception if the test fails
274      */
275     @Test
276     @Alerts("ab,a")
277     public void match_NotFirstCharacter() throws Exception {
278         testEvaluate("\"ab\".match(/^(.)[^\\1]$/)");
279     }
280 
281     /**
282      * @throws Exception if the test fails
283      */
284     @Test
285     @Alerts("boo();")
286     public void regExp_exec() throws Exception {
287         final String html = "<html><head><script>\n"
288             + LOG_TITLE_FUNCTION
289             + "  function test() {\n"
290             + "    var re = new RegExp('(?:<s' + 'cript.*?>)(.*)<\\/script>');\n"
291             + "    var t = 'foo <scr' + 'ipt>boo();</' + 'script>bar';\n"
292             + "    var r = re.exec(t);\n"
293             + "    log(r[1]);\n"
294             + "  }\n"
295             + "</script></head><body onload='test()'>\n"
296             + "</body></html>";
297 
298         loadPageVerifyTitle2(html);
299     }
300 
301     /**
302      * @throws Exception if the test fails
303      */
304     @Test
305     @Alerts("<script>boo();</script>")
306     public void flag_global() throws Exception {
307         final String html = "<html><head><script>\n"
308             + LOG_TITLE_FUNCTION
309             + "  function test() {\n"
310             + "    var str = 'foo <script>boo();<'+'/script>bar';\n"
311             + "    var regExp = new RegExp('<script[^>]*>([\\\\S\\\\s]*?)<\\/script>', 'img');\n"
312             + "    log(str.match(regExp));\n"
313             + "  }\n"
314             + "</script></head><body onload='test()'>\n"
315             + "</body></html>";
316 
317         loadPageVerifyTitle2(html);
318     }
319 
320     /**
321      * Minimal case exists in {@link #test_minimal()}.  Once successful, either one to be removed.
322      * @throws Exception if the test fails
323      */
324     @Test
325     @Alerts({"true", "false", "true"})
326     public void prototype() throws Exception {
327         final String html = "<html><head><script>\n"
328             + LOG_TITLE_FUNCTION
329             + "  function test() {\n"
330             + "    var regexp = /^(?:(?:(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|"
331             + "(?:(?:16|[2468][048]|[3579][26])00)))(\\/|-|\\.)(?:0?2\\1(?:29)))|(?:(?:(?:1[6-9]|[2-9]\\d)?\\d{2})"
332             + "(\\/|-|\\.)(?:(?:(?:0?[13578]|1[02])\\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\\2(29|30))|(?:(?:0?[1-9])|"
333             + "(?:1[0-2]))\\2(?:0?[1-9]|1\\d|2[0-8]))))$/;\n"
334             + "    var str = '2001-06-16';\n"
335             + "    log(regexp.test(str));\n"
336             + "    log(regexp.test('hello'));\n"
337             + "    log(regexp.exec(str) != null);\n"
338             + "  }\n"
339             + "</script></head><body onload='test()'>\n"
340             + "</body></html>";
341 
342         loadPageVerifyTitle2(html);
343     }
344 
345     /**
346      * @throws Exception if the test fails
347      */
348     @Test
349     public void test() throws Exception {
350         test("/foo/", "hello foo", true, true, false);
351         test("/foo/gi", "hello foo", true, false, false);
352         test("/foo/gi", "hello Foo", true, false, false);
353     }
354 
355     private void test(final String regexp, final String testString, final boolean... expectedResults) throws Exception {
356         final String html = "<html><head><script>\n"
357             + LOG_TITLE_FUNCTION
358             + "  function test() {\n"
359             + "    var regexp = " + regexp + ";\n"
360             + "    var str = '" + testString + "';\n"
361             + "    log(regexp.test(str));\n"
362             + "    log(regexp.exec(str) != null);\n"
363             + "    log(regexp.test('blabla'));\n"
364             + "  }\n"
365             + "</script></head><body onload='test()'>\n"
366             + "</body></html>";
367 
368         setExpectedAlerts(String.valueOf(expectedResults[0]), String.valueOf(expectedResults[1]),
369             String.valueOf(expectedResults[2]));
370 
371         loadPageVerifyTitle2(html);
372     }
373 
374     /**
375      * Minimized version of {@link #test()}.  Once successful, either one to be removed.
376      * @throws Exception if the test fails
377      */
378     @Test
379     @Alerts("true")
380     public void test_minimal() throws Exception {
381         final String html = "<html><head><script>\n"
382             + LOG_TITLE_FUNCTION
383             + "  function test() {\n"
384             + "    var regexp = /((?:2001)-)/;\n"
385             + "    var str = '2001-';\n"
386             + "    log(regexp.test(str));\n"
387             + "  }\n"
388             + "</script></head><body onload='test()'>\n"
389             + "</body></html>";
390 
391         loadPageVerifyTitle2(html);
392     }
393 
394     /**
395      * Tests usage of regex with non escaped curly braces, such as is used by dhtmlGrid.
396      * @throws Exception if the test fails
397      */
398     @Test
399     @Alerts("{#abcd},{,abcd,}")
400     public void regexWithNonEscapedCurlyBraces() throws Exception {
401         final String html = "<html><head><script>\n"
402             + LOG_TITLE_FUNCTION
403             + "  function test() {\n"
404             + "    var regexp = /(^|{)#([^}]+)(}|$)/;\n"
405             + "    var str = '|{#abcd}|';\n"
406             + "    log(str.match(regexp));\n"
407             + "  }\n"
408             + "</script></head><body onload='test()'>\n"
409             + "</body></html>";
410 
411         loadPageVerifyTitle2(html);
412     }
413 
414     /**
415      * Tests usage of regex with non escaped curly braces, such as is used by dhtmlGrid.
416      * @throws Exception if the test fails
417      */
418     @Test
419     @Alerts("aa-b-b-")
420     public void backSpace() throws Exception {
421         final String html = "<html><head><script>\n"
422             + LOG_TITLE_FUNCTION
423             + "  function test() {\n"
424             + "    var regexp = /[p\\bz]/g;\n"
425             + "    var str = 'aapbzb' + String.fromCharCode(8);\n"
426             + "    log(str.replace(regexp, '-'));\n"
427             + "  }\n"
428             + "</script></head><body onload='test()'>\n"
429             + "</body></html>";
430 
431         loadPageVerifyTitle2(html);
432     }
433 
434     /**
435      * @throws Exception if the test fails
436      */
437     @Test
438     @Alerts("")
439     public void dollarSignAndCurlyBracket() throws Exception {
440         testEvaluate("''.replace(/\\${/g, '')");
441     }
442 
443     /**
444      * @throws Exception if the test fails
445      */
446     @Test
447     @Alerts({"null", "["})
448     public void openingSquareBracketInCharacterClass() throws Exception {
449         final String html = "<html><head><script>\n"
450             + LOG_TITLE_FUNCTION
451             + "function test() {\n"
452             + "  var re = /[[]/;\n"
453             + "  log('div'.match(re));\n"
454             + "  log('['.match(re));\n"
455             + "  }\n"
456             + "</script></head><body onload='test()'>\n"
457             + "</body></html>";
458 
459         loadPageVerifyTitle2(html);
460     }
461 
462     /**
463      * RegExp used in JQuery 1.3.1 that wasn't correctly transformed to Java RegExp in HtmlUnit-2.4.
464      * @throws Exception if the test fails
465      */
466     @Test
467     @Alerts("div")
468     public void jquerySizzleChunker() throws Exception {
469         final String html = "<html><head><script>\n"
470             + LOG_TITLE_FUNCTION
471             + " var re = /((?:\\((?:\\([^()]+\\)|[^()]+)+\\)|\\[(?:\\[[^[\\]]*\\]|['\"][^'\"]+['\"]|[^[\\]'\"]+)+\\]"
472             + "|\\\\.|[^ >+~,(\\[]+)+|[>+~])(\\s*,\\s*)?/g\n"
473             + "  function test() {\n"
474             + "    log('div'.match(re));\n"
475             + "  }\n"
476             + "</script></head><body onload='test()'>\n"
477             + "</body></html>";
478 
479         loadPageVerifyTitle2(html);
480     }
481 
482     /**
483      * RegExp used in JQuery 1.3.1 that wasn't correctly transformed to Java RegExp in HtmlUnit-2.4.
484      * @throws Exception if the test fails
485      */
486     @Test
487     @Alerts({":toto,toto,,", "null"})
488     public void jqueryPseudo() throws Exception {
489         final String html = "<html><head><script>\n"
490             + LOG_TITLE_FUNCTION
491             + " var re = /:((?:[\\w\\u00c0-\\uFFFF_-]|\\\\.)+)(?:\\((['\"]*)((?:\\([^\\)]+\\)"
492             + "|[^\\2\\(\\)]*)+)\\2\\))?/;\n"
493             + "  function test() {\n"
494             + "    log(':toto'.match(re));\n"
495             + "    log('foo'.match(re));\n"
496             + "  }\n"
497             + "</script></head><body onload='test()'>\n"
498             + "</body></html>";
499 
500         loadPageVerifyTitle2(html);
501     }
502 
503     /**
504      * Sample from ExtJS.
505      * @throws Exception if the test fails
506      */
507     @Test
508     @Alerts({ "[floating=true],floating,=,,true",
509                 "[floating=\"true\"],floating,=,\",true",
510                 "[floating=\"true'],floating,=,,\"true'",
511                 "[floating=\"true],floating,=,,\"true",
512                 "[floating=true\"],floating,=,,true\""})
513     public void extJs() throws Exception {
514         final String html = "<html><head><script>\n"
515             + LOG_TITLE_FUNCTION
516             + "  var re = /^(?:\\[((?:[@?$])?[\\w\\-]*)\\s*(?:([\\^$*~%!\\/]?=)\\s*(['\\\"])?((?:\\\\\\]|.)*?)\\3)?(?!\\\\)\\])/;\n"
517             + "  function test() {\n"
518             + "    log('[floating=true]'.match(re));\n"
519             + "    log('[floating=\"true\"]'.match(re));\n"
520             + "    log('[floating=\"true\\']'.match(re));\n"
521             + "    log('[floating=\"true]'.match(re));\n"
522             + "    log('[floating=true\"]'.match(re));\n"
523             + "  }\n"
524             + "</script></head><body onload='test()'>\n"
525             + "</body></html>";
526 
527         loadPageVerifyTitle2(html);
528     }
529 
530     /**
531      * Javascript has a different idea about back references to optional groups.
532      * @throws Exception if the test fails
533      */
534     @Test
535     @Alerts({"axxxxa,a", "xxxx,", "xxxx,", "xxxx,"})
536     public void backReferenceToOptionalGroup() throws Exception {
537         final String html = "<html><head><script>\n"
538             + LOG_TITLE_FUNCTION
539             + "function test() {\n"
540             + "  var re = /(a)?x+\\1/;\n"
541             + "  log('axxxxa'.match(re));\n"
542             + "  log('axxxx'.match(re));\n"
543             + "  log('xxxxa'.match(re));\n"
544             + "  log('xxxx'.match(re));\n"
545             + "}\n"
546             + "</script></head><body onload='test()'>\n"
547             + "</body></html>";
548 
549         loadPageVerifyTitle2(html);
550     }
551 
552     /**
553      * Browsers ignore back references pointing to not finished groups.
554      * @throws Exception if the test fails
555      */
556     @Test
557     @Alerts({"abcx\u0004,b,x", "null", "null", "null"})
558     public void backReferenceToNotDefinedGroupsAreHandledAsOctal() throws Exception {
559         final String html = "<html><head>\n<script>\n"
560             + LOG_TEXTAREA_FUNCTION
561             + "function test() {\n"
562             + "  var re = /(?:a)(b)c(x)\\4/;\n"
563             + "  log('abcx\\04'.match(re));\n"
564             + "  log('abcx'.match(re));\n"
565             + "  log('abb'.match(re));\n"
566             + "  log('abbxx'.match(re));\n"
567             + "}\n"
568             + "</script></head><body onload='test()'>\n"
569             + LOG_TEXTAREA
570             + "</body></html>";
571 
572         loadPageVerifyTextArea2(html);
573     }
574 
575     /**
576      * Browsers ignore back references pointing to not finished groups.
577      * @throws Exception if the test fails
578      */
579     @Test
580     @Alerts({"abcx,b,x", "abcx,b,x", "null", "null"})
581     public void ignoreBackReferenceNotFinishedGroups() throws Exception {
582         final String html = "<html><head><script>\n"
583             + LOG_TITLE_FUNCTION
584             + "function test() {\n"
585             + "  var re = /(?:a)(b)c(x\\2)/;\n"
586             + "  log('abcx'.match(re));\n"
587             + "  log('abcx\\02'.match(re));\n"
588             + "  log('abb'.match(re));\n"
589             + "  log('abbxx'.match(re));\n"
590             + "}\n"
591             + "</script></head><body onload='test()'>\n"
592             + "</body></html>";
593 
594         loadPageVerifyTitle2(html);
595     }
596 
597     /**
598      * Browsers ignore back references in character classes.
599      * This is a simplified case of {@link #jqueryPseudo()}
600      * @throws Exception if the test fails
601      */
602     @Test
603     @Alerts({"null", "abb,a,b", "abd,a,b"})
604     public void ignoreBackReferenceInCharacterClass() throws Exception {
605         final String html = "<html><head><script>\n"
606             + LOG_TITLE_FUNCTION
607             + "function test() {\n"
608             + "  var re = /(a)(b)[^\\2c]/;\n"
609             + "  log('abc'.match(re));\n"
610             + "  log('abb'.match(re));\n"
611             + "  log('abd'.match(re));\n"
612             + "}\n"
613             + "</script></head><body onload='test()'>\n"
614             + "</body></html>";
615 
616         loadPageVerifyTitle2(html);
617     }
618 
619     /**
620      * Tests basic usage of back references in a <tt>string.replace(...)</tt> invocation.
621      * @throws Exception if an error occurs
622      */
623     @Test
624     @Alerts("afood$0$7b")
625     public void replace_backReferences() throws Exception {
626         testEvaluate("'afoob'.replace(/(foo)/g, '$1d$0$7')");
627     }
628 
629     /**
630      * Use back reference in replacement when it exists.
631      * @throws Exception if an error occurs
632      */
633     @Test
634     @Alerts("hllo")
635     public void replace_backReference_existing() throws Exception {
636         testEvaluate("'hello'.replace(/(h)e/g, '$1')");
637     }
638 
639     /**
640      * If no back reference is present, use $ as litteral.
641      * @throws Exception if an error occurs
642      */
643     @Test
644     @Alerts("$1llo")
645     public void replace_backReference_notExisting() throws Exception {
646         testEvaluate("'hello'.replace(/he/g, '$1')");
647     }
648 
649     /**
650      * Regression test for bug 2638813 (dollar signs with no index are not back references).
651      * @throws Exception if an error occurs
652      */
653     @Test
654     @Alerts("I$want$these$periods$to$be$$s")
655     public void replace_backReferences_2() throws Exception {
656         testEvaluate("'I.want.these.periods.to.be.$s'.replace(/\\./g, '$')");
657     }
658 
659     /**
660      * @throws Exception if an error occurs
661      */
662     @Test
663     @Alerts("food bar")
664     public void replace_backReference_ampersand() throws Exception {
665         testEvaluate("'foo bar'.replace(/foo/g, '$&d')");
666         testEvaluate("'foo bar'.replace(/foo/, '$&d')");
667     }
668 
669     /**
670      * @throws Exception if an error occurs
671      */
672     @Test
673     @Alerts("foo foo ")
674     public void replace_backReference_backtick()  throws Exception {
675         testEvaluate("'foo bar'.replace(/bar/g, '$`')");
676         testEvaluate("'foo bar'.replace(/bar/, '$`')");
677     }
678 
679     /**
680      * @throws Exception if an error occurs
681      */
682     @Test
683     @Alerts(" bar bar")
684     public void replace_backReference_tick() throws Exception {
685         testEvaluate("'foo bar'.replace(/foo/g, '$\\'')");
686         testEvaluate("'foo bar'.replace(/foo/, '$\\'')");
687     }
688 
689     /**
690      * @throws Exception if an error occurs
691      */
692     @Test
693     @Alerts("$' bar")
694     public void replace_$backReference_tick() throws Exception {
695         testEvaluate("'foo bar'.replace(/foo/g, '$$\\'')");
696         testEvaluate("'foo bar'.replace(/foo/, '$$\\'')");
697     }
698 
699     /**
700      * @throws Exception if an error occurs
701      */
702     @Test
703     @Alerts("ad$0db")
704     public void replace_backReference_$0() throws Exception {
705         testEvaluate("'afoob'.replace(/(foo)/g, 'd$0d')");
706         testEvaluate("'afoob'.replace(/(foo)/, 'd$0d')");
707     }
708 
709     /**
710      * @throws Exception if an error occurs
711      */
712     @Test
713     @Alerts("ad$0db")
714     public void replace_backReference_$0WithMultipleGroups() throws Exception {
715         testEvaluate("'afkxxxkob'.replace(/(f)k(.*)k(o)/g, 'd$0d')");
716         testEvaluate("'afkxxxkob'.replace(/(f)k(.*)k(o)/, 'd$0d')");
717     }
718 
719     /**
720      * @throws Exception if an error occurs
721      */
722     @Test
723     @Alerts("ad$0db")
724     public void replace_backReference_$0WithNoGroups() throws Exception {
725         testEvaluate("'afoob'.replace(/foo/g, 'd$0d')");
726         testEvaluate("'afoob'.replace(/foo/, 'd$0d')");
727     }
728 
729     /**
730      * @throws Exception if an error occurs
731      */
732     @Test
733     @Alerts("ad$0dbfuoc")
734     public void replace_backReference_$0WithMultipleHits() throws Exception {
735         testEvaluate("'afoobfuoc'.replace(/(f.o)/, 'd$0d')");
736     }
737 
738     /**
739      * @throws Exception if an error occurs
740      */
741     @Test
742     @Alerts("ad$0dbd$0dc")
743     public void replace_backReference_$0WithMultipleHitsGlobal() throws Exception {
744         testEvaluate("'afoobfuoc'.replace(/(f.o)/g, 'd$0d')");
745     }
746 
747     /**
748      * @throws Exception if an error occurs
749      */
750     @Test
751     @Alerts("kid\\'s toys")
752     public void escapeQuote() throws Exception {
753         testEvaluate("\"kid's toys\".replace(/'/g, \"\\\\'\")");
754     }
755 
756     private static String buildHtml(final String script) {
757         return "<html><head><script>\n"
758             + LOG_TEXTAREA_FUNCTION
759             + "function test() {\n"
760             + script
761             + "\n}</script>\n"
762             + "</head>\n"
763             + "<body onload='test()'>\n"
764             + LOG_TEXTAREA
765             + "</body></html>";
766     }
767 
768     /**
769      * @throws Exception if an error occurs
770      */
771     @Test
772     @Alerts("INPUT")
773     public void test2() throws Exception {
774         final String html = buildHtml("var description = 'INPUT#BasisRenameInput';\n"
775                 + "if(description.match(/^\\s*([a-z0-9\\_\\-]+)/i)) {\n"
776                 + "  log(RegExp.$1);\n"
777                 + "}");
778         loadPageVerifyTextArea2(html);
779     }
780 
781     /**
782      * @throws Exception if an error occurs
783      */
784     @Test
785     @Alerts("a")
786     public void stackOverflow() throws Exception {
787         final String s = IOUtils.toString(getClass().getResourceAsStream("stackOverflow.txt"),
788                 ISO_8859_1);
789         final String html = buildHtml(
790                   "var s = '" + s + "';\n"
791                 + "s = s.replace(/(\\s*\\S+)*/, 'a');\n"
792                 + "log(s);\n");
793         loadPageVerifyTextArea2(html);
794     }
795 
796     /**
797      * Regression test for bug 2890953.
798      * @throws Exception if an error occurs
799      */
800     @Test
801     @Alerts("$$x$")
802     public void replaceDollarDollar() throws Exception {
803         testEvaluate("'x'.replace(/(x)/g, '$$$$x$$')");
804     }
805 
806     /**
807      * Original test resides in
808      * <a href="http://code.google.com/p/google-web-toolkit/source/browse/trunk/user/test/com/google/gwt/emultest/java/lang/StringTest.java">StringTest</a>.
809      *
810      * @throws Exception if the test fails
811      */
812     @Test
813     @Alerts({"\\*\\[", "\\\\", "+1", "abcdef", "1\\1abc123\\123de1234\\1234f", "\n  \n", "x  x", "x\"\\", "$$x$"})
814     public void replaceAll() throws Exception {
815         final String html = "<html>\n"
816             + "<head>\n"
817             + "  <script>\n"
818             + LOG_TEXTAREA_FUNCTION
819             + "    function test() {\n"
820             + "      var regex, replacement, x1, x2, x3, x4, x5;\n"
821             + "      regex = $replaceAll('*[', "
822             + "'([/\\\\\\\\\\\\.\\\\*\\\\+\\\\?\\\\|\\\\(\\\\)\\\\[\\\\]\\\\{\\\\}])', '\\\\\\\\$1');\n"
823             + "      log(regex);\n"
824             + "      replacement = "
825             + "$replaceAll($replaceAll('\\\\', '\\\\\\\\', '\\\\\\\\\\\\\\\\'), '\\\\$', '\\\\\\\\$');\n"
826             + "      log(replacement);\n"
827             + "      log($replaceAll('*[1', regex, '+'));\n"
828             + "      x1 = 'xxxabcxxdexf';\n"
829             + "      log($replaceAll(x1, 'x*', ''));\n"
830             + "      x2 = '1abc123de1234f';\n"
831             + "      log($replaceAll(x2, '([1234]+)', '$1\\\\\\\\$1'));\n"
832             + "      x3 = 'x  x';\n"
833             + "      log($replaceAll(x3, 'x', '\\n'));\n"
834             + "      x4 = 'x  \\n';\n"
835             + "      log($replaceAll(x4, '\\\\\\n', 'x'));\n"
836             + "      x5 = 'x';\n"
837             + "      log($replaceAll(x5, 'x', '\\\\x\\\\\"\\\\\\\\'));\n"
838             + "      log($replaceAll(x5, '(x)', '\\\\$\\\\$$1\\\\$'));\n"
839             + "    }\n"
840             + "    function $replaceAll(this$static, regex, replace){\n"
841             + "      replace = __translateReplaceString(replace);\n"
842             + "      return this$static.replace(RegExp(regex, 'g'), replace);\n"
843             + "    }\n"
844             + "    function __translateReplaceString(replaceStr){\n"
845             + "      var pos = 0;\n"
846             + "      while (0 <= (pos = replaceStr.indexOf('\\\\', pos))) {\n"
847             + "        if (replaceStr.charCodeAt(pos + 1) == 36) {\n"
848             + "          replaceStr = replaceStr.substr(0, pos - 0) + '$' + $substring(replaceStr, ++pos);\n"
849             + "        }\n"
850             + "        else {\n"
851             + "          replaceStr = replaceStr.substr(0, pos - 0) + $substring(replaceStr, ++pos);\n"
852             + "        }\n"
853             + "      }\n"
854             + "      return replaceStr;\n"
855             + "    }\n"
856             + "    function $substring(this$static, beginIndex){\n"
857             + "      return this$static.substr(beginIndex, this$static.length - beginIndex);\n"
858             + "    }\n"
859             + "  </script>\n"
860             + "</head><body onload='test()'>\n"
861             + LOG_TEXTAREA
862             + "</body></html>";
863 
864         loadPageVerifyTextArea2(html);
865     }
866 
867     /**
868      * Regression test for bug 2890953.
869      * @throws Exception if an error occurs
870      */
871     @Test
872     @Alerts("\\$0")
873     public void replaceBackslashDollar() throws Exception {
874         testEvaluate("'$0'.replace(/\\$/g, '\\\\$')");
875     }
876 
877     private void testEvaluate(final String expression) throws Exception {
878         final String script = "log(" + expression + ");";
879         final String html = buildHtml(script);
880 
881         loadPageVerifyTextArea2(html);
882     }
883 
884     /**
885      * Regression test for bug 2879412.
886      * In RegularExpression, backslash followed by a letter that has no special
887      * signification are ignored.
888      * @throws Exception if an error occurs
889      */
890     @Test
891     @Alerts("null")
892     public void backslash() throws Exception {
893         final String html = buildHtml("var regexp = /(\\https:\\/\\/)/;\n"
894                 + "var url = 'http://localhost/test.html';\n"
895                 + "log(url.match(regexp));");
896         loadPageVerifyTextArea2(html);
897     }
898 
899     /**
900      * Regression test for bug 2906293.
901      * @throws Exception if an error occurs
902      */
903     @Test
904     @Alerts("\\\\$11")
905     public void dollar() throws Exception {
906         testEvaluate("'\\\\$1'.replace(/\\$/g, '\\\\$1')");
907     }
908 
909     /**
910      * @throws Exception if the test fails
911      */
912     @Test
913     @Alerts({"foobar", "$0bar", "$1bar", "\\$1bar", "\\1", "cb", "cb", "a$$b", "a$1b", "a$`b", "a$'b"})
914     public void replaceString() throws Exception {
915         final String html = "<html>\n"
916             + "<head>\n"
917             + "  <script>\n"
918             + LOG_TITLE_FUNCTION
919             + "    function test() {\n"
920             + "      log($replace('bazbar', 'baz', 'foo'));\n"
921             + "      log($replace('foobar', 'foo', '$0'));\n"
922             + "      log($replace('foobar', 'foo', '$1'));\n"
923             + "      log($replace('foobar', 'foo', '\\\\$1'));\n"
924             + "      log($replace('*[)1', '*[)', '\\\\'));\n"
925             + "      log($replace('$ab', '$a', 'c'));\n"
926             + "      log($replace('^ab', '^a', 'c'));\n"
927             + "      log($replace('a[x]b', '[x]', '$$'));\n"
928             + "      log($replace('a[x]b', '[x]', '$1'));\n"
929             + "      log($replace('a[x]b', '[x]', '$`'));\n"
930             + "      log($replace('a[x]b', '[x]', \"$'\"));\n"
931             + "    }\n"
932             + "    function $replace(this$static, from, to){\n"
933             + "      var regex, replacement;\n"
934             + "      regex = $replaceAll(from, "
935             + "'([/\\\\\\\\\\\\.\\\\*\\\\+\\\\?\\\\|\\\\(\\\\)\\\\[\\\\]\\\\{\\\\}$^])', '\\\\\\\\$1');\n"
936             + "      replacement = $replaceAll("
937             + "$replaceAll(to, '\\\\\\\\', '\\\\\\\\\\\\\\\\'), '\\\\$', '\\\\\\\\$');\n"
938             + "      return $replaceAll(this$static, regex, replacement);\n"
939             + "    }\n"
940             + "    function $replaceAll(this$static, regex, replace){\n"
941             + "      replace = __translateReplaceString(replace);\n"
942             + "      return this$static.replace(RegExp(regex, 'g'), replace);\n"
943             + "    }\n"
944             + "    function __translateReplaceString(replaceStr){\n"
945             + "      var pos = 0;\n"
946             + "      while (0 <= (pos = replaceStr.indexOf('\\\\', pos))) {\n"
947             + "        if (replaceStr.charCodeAt(pos + 1) == 36) {\n"
948             + "          replaceStr = replaceStr.substr(0, pos - 0) + '$' + $substring(replaceStr, ++pos);\n"
949             + "        }\n"
950             + "        else {\n"
951             + "          replaceStr = replaceStr.substr(0, pos - 0) + $substring(replaceStr, ++pos);\n"
952             + "        }\n"
953             + "      }\n"
954             + "      return replaceStr;\n"
955             + "    }\n"
956             + "    function $substring(this$static, beginIndex){\n"
957             + "      return this$static.substr(beginIndex, this$static.length - beginIndex);\n"
958             + "    }\n"
959             + "  </script>\n"
960             + "</head><body onload='test()'>\n"
961             + "</body></html>";
962 
963         loadPageVerifyTitle2(html);
964     }
965 
966     /**
967      * Regression test for bug 2949446.
968      * @throws Exception if an error occurs
969      */
970     @Test
971     @Alerts("[k]")
972     public void replace_group2digits() throws Exception {
973         testEvaluate("'abcdefghijkl'.replace(/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)/g, '[\\$11]')");
974     }
975 
976     /**
977      * When replacement reference is two digits but not so much groups exist, one digit replacement index is taken.
978      * @throws Exception if an error occurs
979      */
980     @Test
981     @Alerts("[a8]")
982     public void replace_group2digits_doesntExist() throws Exception {
983         testEvaluate("'abcdefghijkl'.replace(/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)/g, '[\\$18]')");
984     }
985 
986     /**
987      * When replacement reference is two digits but not so much groups exist, one digit replacement index is taken.
988      * @throws Exception if an error occurs
989      */
990     @Test
991     @Alerts(":")
992     public void replaceUnicode() throws Exception {
993         testEvaluate("'\\u003a'.replace(/[\\u00c0-\\u00c1]/, 'cat')");
994     }
995 
996     /**
997      * Regression test for bug 3005485.
998      * @throws Exception if an error occurs
999      */
1000     @Test
1001     @Alerts("part1:part2")
1002     public void replace_laterFilledGroup() throws Exception {
1003         testEvaluate("'#part1\\:part2'.replace(/^#|\\\\(:)/g, '$1')");
1004     }
1005 
1006     /**
1007      * Test for bug 2969230.
1008      * @throws Exception if an error occurs
1009      */
1010     @Test
1011     @Alerts("\\[ab\\]\\(xy\\)\\{z\\} \\{\\[\\(o\\}\\]\\)")
1012     public void replace_EscapeBrackets() throws Exception {
1013         testEvaluate("'[ab](xy){z} {[(o}])'.replace(/([.*+?^${}()|[\\]\\/\\\\])/g, \"\\\\$1\")");
1014     }
1015 
1016     /**
1017      * Test for bug 3000784.
1018      * @throws Exception if an error occurs
1019      */
1020     @Test
1021     @Alerts("><")
1022     public void replace_T() throws Exception {
1023         testEvaluate("'>' + 'a\\\\.b'.replace(/([\\u00C0-\\uFFFF]|[\\w]|\\\\.)+/, '') + '<'");
1024     }
1025 
1026     /**
1027      * Test from Prototype suite running very fast with Java 6 but taking ages with Java 7 &amp; 8.
1028      * @throws Exception if an error occurs
1029      */
1030     @Test(timeout = 1000)
1031     @Alerts({"2200915", "2000915"})
1032     public void replace_huge() throws Exception {
1033         final String html = "<html><body><script>\n"
1034             + LOG_TITLE_FUNCTION
1035             + "String.prototype.times = function(n) {\n"
1036             + "  var s = '';\n"
1037             + "  for (var i =0; i < n; i++) {\n"
1038             + "    s += this;\n"
1039             + "  }\n"
1040             + "  return s;\n"
1041             + "}\n"
1042             + "var size = 100;\n"
1043             + "var longString = '\"' + '123456789\\\\\"'.times(size * 10) + '\"';\n"
1044             + "var object = '{' + longString + ': ' + longString + '},';\n"
1045             + "var huge = '[' + object.times(size) + '{\"test\": 123}]';\n"
1046             + "log(huge.length);\n"
1047             + "log(huge.replace(/\\\\./g, '@').length);\n"
1048             + "</script>\n"
1049             + "</body></html>";
1050 
1051         loadPageVerifyTitle2(html);
1052     }
1053 
1054     /**
1055      * @throws Exception if the test fails
1056      */
1057     @Test
1058     @Alerts({"true", "true", "true", "true", "true", "true", "true"})
1059     public void nullCharacter() throws Exception {
1060         final String html = "<html><head><script>\n"
1061             + LOG_TITLE_FUNCTION
1062             + "  function test() {\n"
1063             + "    var regex = new RegExp('[\\0-\\x08]');\n"
1064             + "    log(regex.test('\\0'));\n"
1065             + "    log('\\0'.match(regex) != null);\n"
1066             + "    regex = new RegExp('[\\\\0-\\x08]');\n"
1067             + "    log(regex.test('\\0'));\n"
1068             + "    log('\\0'.match(regex) != null);\n"
1069             + "    regex = new RegExp('[\\\\\\0-\\x08]');\n"
1070             + "    log(regex.test('\\0'));\n"
1071             + "    log('\\0'.match(regex) != null);\n"
1072             + "    log('\\0'.match(/^\\0/) != null);\n"
1073             + "  }\n"
1074             + "</script></head><body onload='test()'>\n"
1075             + "</body></html>";
1076 
1077         loadPageVerifyTitle2(html);
1078     }
1079 
1080     /**
1081      * @throws Exception if the test fails
1082      */
1083     @Test
1084     @Alerts("abc")
1085     public void specialBrackets1() throws Exception {
1086         // [] matches no character in JS
1087         testEvaluate("'abc'.replace(/[]/, 'x')");
1088     }
1089 
1090     /**
1091      * @throws Exception if the test fails
1092      */
1093     @Test
1094     @Alerts("")
1095     public void specialBrackets2() throws Exception {
1096         // [] matches no character in JS
1097         testEvaluate("'abc'.match(/[]*/)[0]");
1098     }
1099 
1100     /**
1101      * @throws Exception if the test fails
1102      */
1103     @Test
1104     @Alerts("xaxbxcx")
1105     public void specialBrackets3() throws Exception {
1106         // [] matches no character in JS
1107         testEvaluate("'abc'.replace(/[]*/g, 'x')");
1108     }
1109 
1110     /**
1111      * @throws Exception if the test fails
1112      */
1113     @Test
1114     @Alerts("xaxbxxcx*xdx")
1115     public void specialBrackets4() throws Exception {
1116         // [] matches no character in JS
1117         testEvaluate("'ab]c*d'.replace(/[]*]*/g, 'x')");
1118     }
1119 
1120     /**
1121      * @throws Exception if the test fails
1122      */
1123     @Test
1124     @Alerts("xxx")
1125     public void specialBrackets5() throws Exception {
1126         // [^] matches any character in JS
1127         testEvaluate("'abc'.replace(/[^]/g, 'x')");
1128     }
1129 
1130     /**
1131      * @throws Exception if the test fails
1132      */
1133     @Test
1134     @Alerts("ab\nc")
1135     public void specialBrackets6() throws Exception {
1136         // [^] matches any character in JS
1137         testEvaluate("'ab\\nc'.match(/[^]*/)[0]");
1138     }
1139 
1140     /**
1141      * @throws Exception if the test fails
1142      */
1143     @Test
1144     @Alerts("axcd")
1145     public void specialBrackets7() throws Exception {
1146         // [^] matches any character in JS
1147         testEvaluate("'ab]cd'.replace(/[^]]/g, 'x')");
1148     }
1149 
1150     /**
1151      * @throws Exception if the test fails
1152      */
1153     @Test
1154     @Alerts("xcd")
1155     public void specialBrackets8() throws Exception {
1156         // [^] matches any character in JS
1157         testEvaluate("'abdcd'.replace(/a[^]d/g, 'x')");
1158     }
1159 
1160     /**
1161      * @throws Exception if the test fails
1162      */
1163     @Test
1164     @Alerts("x abdu")
1165     public void specialBrackets9() throws Exception {
1166         // [^] matches any character in JS
1167         testEvaluate("'abdo abdu'.replace(/a[^]d[o]/g, 'x')");
1168     }
1169 
1170     /**
1171      * @throws Exception if the test fails
1172      */
1173     @Test
1174     @Alerts("axbxc d efg 1 23")
1175     public void regExMinusInRangeBorderCase1() throws Exception {
1176         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-]+/g, 'x')");
1177     }
1178 
1179     /**
1180      * @throws Exception if the test fails
1181      */
1182     @Test
1183     @Alerts("axbxcxdxefgx1x23")
1184     public void regExMinusInRangeBorderCase2() throws Exception {
1185         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\s]+/g, 'x')");
1186     }
1187 
1188     /**
1189      * @throws Exception if the test fails
1190      */
1191     @Test
1192     @Alerts("x x x x x")
1193     public void regExMinusInRangeBorderCase3() throws Exception {
1194         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\S]+/g, 'x')");
1195     }
1196 
1197     /**
1198      * @throws Exception if the test fails
1199      */
1200     @Test
1201     @Alerts("x x x x x")
1202     public void regExMinusInRangeBorderCase4() throws Exception {
1203         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\w]+/g, 'x')");
1204     }
1205 
1206     /**
1207      * @throws Exception if the test fails
1208      */
1209     @Test
1210     @Alerts("axbxcxdxefgx1x23")
1211     public void regExMinusInRangeBorderCase5() throws Exception {
1212         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\W]+/g, 'x')");
1213     }
1214 
1215     /**
1216      * @throws Exception if the test fails
1217      */
1218     @Test
1219     @Alerts("axbxc d efg x x")
1220     public void regExMinusInRangeBorderCase6() throws Exception {
1221         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\d]+/g, 'x')");
1222     }
1223 
1224     /**
1225      * @throws Exception if the test fails
1226      */
1227     @Test
1228     @Alerts("x1x23")
1229     public void regExMinusInRangeBorderCase7() throws Exception {
1230         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\D]+/g, 'x')");
1231     }
1232 
1233     /**
1234      * @throws Exception if the test fails
1235      */
1236     @Test
1237     @Alerts("x-bxc d efg 1 23")
1238     public void regExMinusInRangeBorderCase8() throws Exception {
1239         testEvaluate("'a-b_c d efg 1 23'.replace(/[_-\\a]+/g, 'x')");
1240     }
1241 
1242     /**
1243      * @throws Exception if the test fails
1244      */
1245     @Test
1246     @Alerts("https://www.youtube.com/watch?v=1234567,https,1234567")
1247     public void realWorld1() throws Exception {
1248         testEvaluate("'https://www.youtube.com/watch?v=1234567'.match(/^(?:(https?):\\/\\/)?(?:(?:www|m)\\.)?youtube\\.com\\/watch.*v=([a-zA-Z0-9_-]+)/)");
1249     }
1250 
1251     /**
1252      * @throws Exception if the test fails
1253      */
1254     @Test
1255     @Alerts("http://m.youtu.be/abcd,http,abcd")
1256     public void realWorld2() throws Exception {
1257         testEvaluate("'http://m.youtu.be/abcd'.match(/^(?:(https?):\\/\\/)?(?:(?:www|m)\\.)?youtu\\.be\\/([a-zA-Z0-9_-]+)/)");
1258     }
1259 }