Bug 593663: emulate flat regexps in three-argument String.prototype.replace. (r=lw)

This commit is contained in:
Chris Leary 2010-09-22 10:27:38 -07:00
Родитель 2e4ff06ce2
Коммит b4f07e8236
3 изменённых файлов: 160 добавлений и 12 удалений

Просмотреть файл

@ -107,6 +107,7 @@ class RegExp
#endif
}
static bool isMetaChar(jschar c);
static bool hasMetaChars(const jschar *chars, size_t length);
/*
@ -438,19 +439,26 @@ RegExp::compile(JSContext *cx)
return compileHelper(cx, *fakeySource);
}
inline bool
RegExp::isMetaChar(jschar c)
{
switch (c) {
/* Taken from the PatternCharacter production in 15.10.1. */
case '^': case '$': case '\\': case '.': case '*': case '+':
case '?': case '(': case ')': case '[': case ']': case '{':
case '}': case '|':
return true;
default:
return false;
}
}
inline bool
RegExp::hasMetaChars(const jschar *chars, size_t length)
{
for (size_t i = 0; i < length; ++i) {
jschar c = chars[i];
switch (c) {
/* Taken from the PatternCharacter production in 15.10.1. */
case '^': case '$': case '\\': case '.': case '*': case '+':
case '?': case '(': case ')': case '[': case ']': case '{':
case '}': case '|':
if (isMetaChar(chars[i]))
return true;
default:;
}
}
return false;
}

Просмотреть файл

@ -1689,6 +1689,26 @@ class RegExpGuard
*/
static const size_t MAX_FLAT_PAT_LEN = 256;
static JSString *flattenPattern(JSContext *cx, JSString *patstr) {
JSCharBuffer cb(cx);
if (!cb.reserve(patstr->length()))
return NULL;
static const jschar ESCAPE_CHAR = '\\';
const jschar *chars = patstr->chars();
size_t len = patstr->length();
for (const jschar *it = chars; it != chars + len; ++it) {
if (RegExp::isMetaChar(*it)) {
if (!cb.append(ESCAPE_CHAR) || !cb.append(*it))
return NULL;
} else {
if (!cb.append(*it))
return NULL;
}
}
return js_NewStringFromCharBuffer(cx, cb);
}
public:
explicit RegExpGuard(JSContext *cx) : cx(cx), rep(NULL) {}
@ -1768,7 +1788,17 @@ class RegExpGuard
opt = NULL;
}
rep.re_ = RegExp::createFlagged(cx, fm.patstr, opt);
JSString *patstr;
if (flat) {
patstr = flattenPattern(cx, fm.patstr);
if (!patstr)
return false;
} else {
patstr = fm.patstr;
}
JS_ASSERT(patstr);
rep.re_ = RegExp::createFlagged(cx, patstr, opt);
if (!rep.re_)
return NULL;
rep.reobj_ = NULL;
@ -2417,9 +2447,10 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
{
ReplaceData rdata(cx);
NORMALIZE_THIS(cx, vp, rdata.str);
static const uint32 optarg = 2;
/* Extract replacement string/function. */
if (argc >= 2 && js_IsCallable(vp[3])) {
if (argc >= optarg && js_IsCallable(vp[3])) {
rdata.lambda = &vp[3].toObject();
rdata.repstr = NULL;
rdata.dollar = rdata.dollarEnd = NULL;
@ -2450,9 +2481,9 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
* |RegExp| statics.
*/
const FlatMatch *fm = rdata.g.tryFlatMatch(rdata.str, 2, argc, false);
const FlatMatch *fm = rdata.g.tryFlatMatch(rdata.str, optarg, argc, false);
if (!fm) {
JS_ASSERT_IF(!rdata.g.hasRegExpPair(), argc > 2);
JS_ASSERT_IF(!rdata.g.hasRegExpPair(), argc > optarg);
return str_replace_regexp(cx, argc, vp, rdata);
}

Просмотреть файл

@ -0,0 +1,109 @@
/*
* Ensure that flat matches with metachars in them don't have their metachars
* interpreted as special.
*/
function isPatternSyntaxError(pattern) {
try {
new RegExp(pattern);
return false;
} catch (e if e instanceof SyntaxError) {
return true;
}
}
// Bug example.
assertEq("1+2".replace("1+2", "$&+3", "g"), "1+2+3");
assertEq("1112".replace("1+2", "", "g"), "1112");
// ^
assertEq("leading text^my hat".replace("^my hat", "", "g"), "leading text");
assertEq("my hat".replace("^my hat", "", "g"), "my hat");
// $
assertEq("leading text$my money".replace("leading text$", "", "g"), "my money");
assertEq("leading text".replace("leading text$", "", "g"), "leading text");
// \
var BSL = '\\';
assertEq(("dir C:" + BSL + "Windoze").replace("C:" + BSL, "", "g"),
"dir Windoze");
assertEq(isPatternSyntaxError("C:" + BSL), true);
// .
assertEq("This is a sentence. It has words.".replace(".", "!", "g"),
"This is a sentence! It has words!");
assertEq("This is an unterminated sentence".replace(".", "", "g"),
"This is an unterminated sentence");
// *
assertEq("Video killed the radio *".replace(" *", "", "g"), "Video killed the radio");
assertEq("aaa".replace("a*", "", "g"), "aaa");
// +
assertEq("On the + side".replace(" +", "", "g"), "On the side");
assertEq("1111111111111".replace("1+", "", "g"), "1111111111111");
// \+
assertEq(("Inverse cone head: " + BSL + "++/").replace(BSL + "+", "", "g"),
"Inverse cone head: +/");
assertEq((BSL + BSL + BSL).replace(BSL + "+", "", "g"),
BSL + BSL + BSL);
// \\+
assertEq((BSL + BSL + "+").replace(BSL + BSL + "+", "", "g"), "");
assertEq((BSL + BSL + BSL).replace(BSL + BSL + "+", "", "g"), (BSL + BSL + BSL));
// \\\
assertEq((BSL + BSL + BSL + BSL).replace(BSL + BSL + BSL, "", "g"), BSL);
assertEq(isPatternSyntaxError(BSL + BSL + BSL), true);
// \\\\
assertEq((BSL + BSL + BSL + BSL).replace(BSL + BSL + BSL + BSL, "", "i"), "");
assertEq((BSL + BSL).replace(BSL + BSL + BSL + BSL, "", "g"), BSL + BSL);
// ?
assertEq("Pressing question?".replace("?", ".", "g"), "Pressing question.");
assertEq("a".replace("a?", "", "g"), "a");
// (
assertEq("(a".replace("(", "", "g"), "a");
// )
assertEq("a)".replace(")", "", "g"), "a");
// ( and )
assertEq("(foo)".replace("(foo)", "", "g"), "");
assertEq("a".replace("(a)", "", "g"), "a");
// [
assertEq("[a".replace("[", "", "g"), "a");
// ]
assertEq("a]".replace("]", "", "g"), "a");
// [ and ]
assertEq("a".replace("[a-z]", "", "g"), "a");
assertEq("You would write your regexp as [a-z]".replace("[a-z]", "", "g"),
"You would write your regexp as ");
// {
assertEq("Numbers may be specified in the interval {1,100}".replace("{1,", "", "g"),
"Numbers may be specified in the interval 100}");
// }
assertEq("Numbers may be specified in the interval {1,100}".replace(",100}", "", "g"),
"Numbers may be specified in the interval {1");
// { and }
assertEq("Numbers may be specified in the interval {1,100}".replace(" {1,100}", "", "g"),
"Numbers may be specified in the interval");
assertEq("aaa".replace("a{1,10}", "", "g"), "aaa");
// |
assertEq("Mr. Gorbachev|Tear down this wall!".replace("|Tear down this wall!", "", "g"),
"Mr. Gorbachev");
assertEq("foobar".replace("foo|bar", "", "g"), "foobar");
print("PASS");