diff --git a/src/org/mozilla/javascript/regexp/NativeRegExp.java b/src/org/mozilla/javascript/regexp/NativeRegExp.java index 07dab9c483..e6d91c25eb 100644 --- a/src/org/mozilla/javascript/regexp/NativeRegExp.java +++ b/src/org/mozilla/javascript/regexp/NativeRegExp.java @@ -171,9 +171,9 @@ public static void init(Context cx, Scriptable scope, boolean sealed) defineProperty(scope, "RegExp", ctor, ScriptableObject.DONTENUM); } - NativeRegExp(Scriptable scope, Object regexpCompiled) + NativeRegExp(Scriptable scope, RECompiled regexpCompiled) { - this.re = (RECompiled)regexpCompiled; + this.re = regexpCompiled; this.lastIndex = 0; ScriptRuntime.setBuiltinProtoAndParent(this, scope, TopLevel.Builtins.RegExp); } @@ -218,7 +218,7 @@ Scriptable compile(Context cx, Scriptable scope, Object[] args) this.lastIndex = thatObj.lastIndex; return this; } - String s = args.length == 0 ? "" : ScriptRuntime.toString(args[0]); + String s = args.length == 0 ? "" : escapeRegExp(args[0]); String global = args.length > 1 && args[1] != Undefined.instance ? ScriptRuntime.toString(args[1]) : null; @@ -230,7 +230,7 @@ Scriptable compile(Context cx, Scriptable scope, Object[] args) @Override public String toString() { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); buf.append('/'); if (re.source.length != 0) { buf.append(re.source); @@ -255,6 +255,30 @@ private static RegExpImpl getImpl(Context cx) return (RegExpImpl) ScriptRuntime.getRegExpProxy(cx); } + private static String escapeRegExp(Object src) { + String s = ScriptRuntime.toString(src); + // Escape any naked slashes in regexp source, see bug #510265 + StringBuilder sb = null; // instantiated only if necessary + int start = 0; + int slash = s.indexOf('/'); + while (slash > -1) { + if (slash == start || s.charAt(slash - 1) != '\\') { + if (sb == null) { + sb = new StringBuilder(); + } + sb.append(s, start, slash); + sb.append("\\/"); + start = slash + 1; + } + slash = s.indexOf('/', slash + 1); + } + if (sb != null) { + sb.append(s, start, s.length()); + s = sb.toString(); + } + return s; + } + private Object execSub(Context cx, Scriptable scopeObj, Object[] args, int matchType) { diff --git a/src/org/mozilla/javascript/regexp/RegExpImpl.java b/src/org/mozilla/javascript/regexp/RegExpImpl.java index ae87736ec8..202669e65c 100644 --- a/src/org/mozilla/javascript/regexp/RegExpImpl.java +++ b/src/org/mozilla/javascript/regexp/RegExpImpl.java @@ -56,7 +56,7 @@ public Object compileRegExp(Context cx, String source, String flags) public Scriptable wrapRegExp(Context cx, Scriptable scope, Object compiled) { - return new NativeRegExp(scope, compiled); + return new NativeRegExp(scope, (RECompiled) compiled); } public Object action(Context cx, Scriptable scope, @@ -136,7 +136,7 @@ private static Object matchOrReplace(Context cx, Scriptable scope, Scriptable topScope = ScriptableObject.getTopLevelScope(scope); if (args.length == 0) { - Object compiled = NativeRegExp.compileRE(cx, "", "", false); + RECompiled compiled = NativeRegExp.compileRE(cx, "", "", false); re = new NativeRegExp(topScope, compiled); } else if (args[0] instanceof NativeRegExp) { re = (NativeRegExp) args[0]; @@ -149,7 +149,7 @@ private static Object matchOrReplace(Context cx, Scriptable scope, } else { opt = null; } - Object compiled = NativeRegExp.compileRE(cx, src, opt, forceFlat); + RECompiled compiled = NativeRegExp.compileRE(cx, src, opt, forceFlat); re = new NativeRegExp(topScope, compiled); } diff --git a/testsrc/doctests/regexp-literals.doctest b/testsrc/doctests/regexp.literals.doctest similarity index 100% rename from testsrc/doctests/regexp-literals.doctest rename to testsrc/doctests/regexp.literals.doctest diff --git a/testsrc/doctests/regexp.source.doctest b/testsrc/doctests/regexp.source.doctest new file mode 100644 index 0000000000..5a9dde4c30 --- /dev/null +++ b/testsrc/doctests/regexp.source.doctest @@ -0,0 +1,90 @@ +js> x = RegExp('') +/(?:)/ +js> x.source + +js> RegExp('a').source +a +js> RegExp('\\\\').source +\\ +js> RegExp('\\\\\\\\').source +\\\\ +js> x = RegExp('/') +/\// +js> x.source +\/ +js> x.test('/') +true +js> x = RegExp('//') +/\/\// +js> x.source +\/\/ +js> x.test('//') +true +js> x = RegExp('\/') +/\// +js> x.source +\/ +js> x.test('/') +true +js> x = RegExp('\\/') +/\// +js> x.source +\/ +js> x.test('/') +true +js> x = RegExp('\\\/') +/\// +js> x.source +\/ +js> x.test('/') +true +js> x = RegExp('\\\\/') +/\\// +js> x.source +\\/ +js> x.test('/') +false +js> x.test('\\/') +true +js> x = RegExp('/abc\/foo\\/bar\\\/xyz/') +/\/abc\/foo\/bar\/xyz\// +js> x.source +\/abc\/foo\/bar\/xyz\/ +js> x.test('/abc/foo/bar/xyz/') +true +js> RegExp('[^/]*') +/[^\/]*/ +js> RegExp('[^\/]*') +/[^\/]*/ +js> RegExp('[^\\/]*') +/[^\/]*/ + +js> /./.compile('') +/(?:)/ +js> /./.compile('a') +/a/ +js> /./.compile('\\\\') +/\\/ +js> /./.compile('\\\\\\\\') +/\\\\/ +js> /./.compile('/') +/\// +js> /./.compile('//') +/\/\// +js> /./.compile('\/') +/\// +js> /./.compile('\\/') +/\// +js> /./.compile('\\\/') +/\// +js> /./.compile('\\\\/') +/\\// +js> /./.compile('/abc\/foo\\/bar\\\/xyz/') +/\/abc\/foo\/bar\/xyz\// +js> /./.compile('[^/]*') +/[^\/]*/ +js> /./.compile('[^\/]*') +/[^\/]*/ +js> /./.compile('[^\\/]*') +/[^\/]*/ +