зеркало из https://github.com/mozilla/gecko-dev.git
Bug 593663: emulate flat regexps in three-argument String.prototype.replace. (r=lw)
This commit is contained in:
Родитель
2e4ff06ce2
Коммит
b4f07e8236
|
@ -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");
|
Загрузка…
Ссылка в новой задаче