зеркало из 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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isMetaChar(jschar c);
|
||||||
static bool hasMetaChars(const jschar *chars, size_t length);
|
static bool hasMetaChars(const jschar *chars, size_t length);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -438,19 +439,26 @@ RegExp::compile(JSContext *cx)
|
||||||
return compileHelper(cx, *fakeySource);
|
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
|
inline bool
|
||||||
RegExp::hasMetaChars(const jschar *chars, size_t length)
|
RegExp::hasMetaChars(const jschar *chars, size_t length)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < length; ++i) {
|
for (size_t i = 0; i < length; ++i) {
|
||||||
jschar c = chars[i];
|
if (isMetaChar(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 '|':
|
|
||||||
return true;
|
return true;
|
||||||
default:;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1689,6 +1689,26 @@ class RegExpGuard
|
||||||
*/
|
*/
|
||||||
static const size_t MAX_FLAT_PAT_LEN = 256;
|
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:
|
public:
|
||||||
explicit RegExpGuard(JSContext *cx) : cx(cx), rep(NULL) {}
|
explicit RegExpGuard(JSContext *cx) : cx(cx), rep(NULL) {}
|
||||||
|
|
||||||
|
@ -1768,7 +1788,17 @@ class RegExpGuard
|
||||||
opt = NULL;
|
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_)
|
if (!rep.re_)
|
||||||
return NULL;
|
return NULL;
|
||||||
rep.reobj_ = NULL;
|
rep.reobj_ = NULL;
|
||||||
|
@ -2417,9 +2447,10 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
|
||||||
{
|
{
|
||||||
ReplaceData rdata(cx);
|
ReplaceData rdata(cx);
|
||||||
NORMALIZE_THIS(cx, vp, rdata.str);
|
NORMALIZE_THIS(cx, vp, rdata.str);
|
||||||
|
static const uint32 optarg = 2;
|
||||||
|
|
||||||
/* Extract replacement string/function. */
|
/* Extract replacement string/function. */
|
||||||
if (argc >= 2 && js_IsCallable(vp[3])) {
|
if (argc >= optarg && js_IsCallable(vp[3])) {
|
||||||
rdata.lambda = &vp[3].toObject();
|
rdata.lambda = &vp[3].toObject();
|
||||||
rdata.repstr = NULL;
|
rdata.repstr = NULL;
|
||||||
rdata.dollar = rdata.dollarEnd = NULL;
|
rdata.dollar = rdata.dollarEnd = NULL;
|
||||||
|
@ -2450,9 +2481,9 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
|
||||||
* |RegExp| statics.
|
* |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) {
|
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);
|
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");
|
Загрузка…
Ссылка в новой задаче