8007718: Make static RegExp properties fully compatible to other engines
Reviewed-by: lagergren, sundar
--- a/nashorn/src/jdk/nashorn/internal/objects/Global.java Thu Feb 07 14:58:41 2013 +0100
+++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java Thu Feb 07 15:33:17 2013 +0100
@@ -48,6 +48,7 @@
import jdk.nashorn.internal.runtime.NativeJavaPackage;
import jdk.nashorn.internal.runtime.OptionsObject;
import jdk.nashorn.internal.runtime.PropertyDescriptor;
+import jdk.nashorn.internal.runtime.RegExpMatch;
import jdk.nashorn.internal.runtime.Scope;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
@@ -337,6 +338,9 @@
// class cache
private ClassCache classCache;
+ // Used to store the last RegExp result to support deprecated RegExp constructor properties
+ private RegExpMatch lastRegExpMatch;
+
private static final MethodHandle EVAL = findOwnMH("eval", Object.class, Object.class, Object.class);
private static final MethodHandle PRINT = findOwnMH("print", Object.class, Object.class, Object[].class);
private static final MethodHandle PRINTLN = findOwnMH("println", Object.class, Object.class, Object[].class);
@@ -1375,13 +1379,6 @@
final ScriptObject regExpProto = getRegExpPrototype();
regExpProto.addBoundProperties(DEFAULT_REGEXP);
- // add hook to support "deprecated" "static" properties of RegExp constructor object
- final ScriptFunction handler = ScriptFunctionImpl.makeFunction(NO_SUCH_METHOD_NAME, NativeRegExp.REGEXP_STATICS_HANDLER);
- builtinRegExp.addOwnProperty(NO_SUCH_PROPERTY_NAME, Attribute.NOT_ENUMERABLE, handler);
-
- // add initial undefined "last successful match" property RegExp
- builtinRegExp.addOwnProperty(NativeRegExp.LAST_REGEXP_MATCH, Attribute.NOT_ENUMERABLE, UNDEFINED);
-
// Error stuff
initErrorObjects();
@@ -1703,4 +1700,13 @@
private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
return MH.findStatic(MethodHandles.publicLookup(), Global.class, name, MH.type(rtype, types));
}
+
+ RegExpMatch getLastRegExpMatch() {
+ return lastRegExpMatch;
+ }
+
+ void setLastRegExpMatch(RegExpMatch regExpMatch) {
+ this.lastRegExpMatch = regExpMatch;
+ }
+
}
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeRegExp.java Thu Feb 07 14:58:41 2013 +0100
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeRegExp.java Thu Feb 07 15:33:17 2013 +0100
@@ -27,10 +27,7 @@
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
-import static jdk.nashorn.internal.runtime.linker.Lookup.MH;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -43,6 +40,7 @@
import jdk.nashorn.internal.objects.annotations.Property;
import jdk.nashorn.internal.objects.annotations.ScriptClass;
import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
+import jdk.nashorn.internal.objects.annotations.Where;
import jdk.nashorn.internal.parser.RegExp;
import jdk.nashorn.internal.runtime.BitVector;
import jdk.nashorn.internal.runtime.JSType;
@@ -57,8 +55,6 @@
*/
@ScriptClass("RegExp")
public final class NativeRegExp extends ScriptObject {
- static final MethodHandle REGEXP_STATICS_HANDLER = findOwnMH("regExpStaticsHandler", Object.class, Object.class, Object.class);
-
/** ECMA 15.10.7.5 lastIndex property */
@Property(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
public Object lastIndex;
@@ -80,8 +76,8 @@
private BitVector groupsInNegativeLookahead;
- // RegExp constructor object. Needed to support RegExp "static" properties,
- private Object constructor;
+ // Reference to global object needed to support static RegExp properties
+ private Global globalObject;
/*
public NativeRegExp() {
@@ -329,7 +325,7 @@
* @param self self reference
* @return the input string for the regexp
*/
- @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE)
+ @Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
public static Object source(final Object self) {
return checkRegExp(self).input;
}
@@ -340,7 +336,7 @@
* @param self self reference
* @return true if this regexp is flagged global, false otherwise
*/
- @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE)
+ @Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
public static Object global(final Object self) {
return checkRegExp(self).global;
}
@@ -351,7 +347,7 @@
* @param self self reference
* @return true if this regexp if flagged to ignore case, false otherwise
*/
- @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE)
+ @Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
public static Object ignoreCase(final Object self) {
return checkRegExp(self).ignoreCase;
}
@@ -362,11 +358,175 @@
* @param self self reference
* @return true if this regexp is flagged to be multiline, false otherwise
*/
- @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE)
+ @Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
public static Object multiline(final Object self) {
return checkRegExp(self).multiline;
}
+ /**
+ * Getter for non-standard RegExp.input property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "input")
+ public static Object getLastInput(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getInput();
+ }
+
+ /**
+ * Getter for non-standard RegExp.multiline property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "multiline")
+ public static Object getLastMultiline(Object self) {
+ return false; // doesn't ever seem to become true and isn't documented anyhwere
+ }
+
+ /**
+ * Getter for non-standard RegExp.lastMatch property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "lastMatch")
+ public static Object getLastMatch(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(0);
+ }
+
+ /**
+ * Getter for non-standard RegExp.lastParen property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "lastParen")
+ public static Object getLastParen(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getLastParen();
+ }
+
+ /**
+ * Getter for non-standard RegExp.leftContext property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "leftContext")
+ public static Object getLeftContext(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getInput().substring(0, match.getIndex());
+ }
+
+ /**
+ * Getter for non-standard RegExp.rightContext property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "rightContext")
+ public static Object getRightContext(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getInput().substring(match.getIndex() + match.length());
+ }
+
+ /**
+ * Getter for non-standard RegExp.$1 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$1")
+ public static Object getGroup1(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(1);
+ }
+
+ /**
+ * Getter for non-standard RegExp.$2 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$2")
+ public static Object getGroup2(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(2);
+ }
+
+ /**
+ * Getter for non-standard RegExp.$3 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$3")
+ public static Object getGroup3(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(3);
+ }
+
+ /**
+ * Getter for non-standard RegExp.$4 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$4")
+ public static Object getGroup4(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(4);
+ }
+
+ /**
+ * Getter for non-standard RegExp.$5 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$5")
+ public static Object getGroup5(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(5);
+ }
+
+ /**
+ * Getter for non-standard RegExp.$6 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$6")
+ public static Object getGroup6(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(6);
+ }
+
+ /**
+ * Getter for non-standard RegExp.$7 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$7")
+ public static Object getGroup7(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(7);
+ }
+
+ /**
+ * Getter for non-standard RegExp.$8 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$8")
+ public static Object getGroup8(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(8);
+ }
+
+ /**
+ * Getter for non-standard RegExp.$9 property.
+ * @param self self object
+ * @return last regexp input
+ */
+ @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$9")
+ public static Object getGroup9(Object self) {
+ final RegExpMatch match = Global.instance().getLastRegExpMatch();
+ return match == null ? "" : match.getGroup(9);
+ }
+
private RegExpMatch execInner(final String string) {
if (this.pattern == null) {
return null; // never matches or similar, e.g. a[]
@@ -389,7 +549,9 @@
setLastIndex(matcher.end());
}
- return new RegExpMatch(string, matcher.start(), groups(matcher));
+ final RegExpMatch match = new RegExpMatch(string, matcher.start(), groups(matcher));
+ globalObject.setLastRegExpMatch(match);
+ return match;
}
/**
@@ -425,84 +587,13 @@
* @return NativeArray of matches, string or null.
*/
public Object exec(final String string) {
- final RegExpMatch m = execInner(string);
- // the input string
- if (m == null) {
- return setLastRegExpMatch(null);
+ final RegExpMatch match = execInner(string);
+
+ if (match == null) {
+ return null;
}
- return setLastRegExpMatch(new NativeRegExpExecResult(m));
- }
-
- // Name of the "last successful match" property of the RegExp constructor
- static final String LAST_REGEXP_MATCH = "__last_regexp_match__";
-
- /**
- * Handles "static" properties of RegExp constructor. These are "deprecated"
- * properties of RegExp constructor.
- *
- * @param self self object passed to this method
- * @param name name of the property being searched
- *
- * @return value of the specified property or undefined if not found
- */
- public static Object regExpStaticsHandler(final Object self, final Object name) {
- final String propName = JSType.toString(name);
- if (self instanceof ScriptObject) {
- final ScriptObject sobj = (ScriptObject)self;
- final Object value = sobj.get(LAST_REGEXP_MATCH);
- if (! (value instanceof NativeRegExpExecResult)) {
- return UNDEFINED;
- }
-
- // get the last match object
- final NativeRegExpExecResult lastMatch = (NativeRegExpExecResult)value;
-
- // look for $1... $9
- if (propName.length() > 0 && propName.charAt(0) == '$') {
- int index = 0;
- try {
- index = Integer.parseInt(propName.substring(1));
- } catch (final Exception ignored) {
- return UNDEFINED;
- }
-
- // index out of range
- if (index < 1 && index > 9) {
- return UNDEFINED;
- }
-
- // retrieve indexed value from last match object.
- return lastMatch.get(index);
- }
-
- // misc. "static" properties supported
- switch (propName) {
- case "input": {
- return lastMatch.input;
- }
-
- case "lastMatch": {
- return lastMatch.get(0);
- }
-
- case "lastParen": {
- final int len = ((Number)NativeRegExpExecResult.length(lastMatch)).intValue();
- return (len > 0)? lastMatch.get(len - 1) : UNDEFINED;
- }
- }
- }
-
- return UNDEFINED;
- }
-
- // Support for RegExp static properties. We set last successful match
- // to the RegExp constructor object.
- private Object setLastRegExpMatch(final Object match) {
- if (constructor instanceof ScriptObject) {
- ((ScriptObject)constructor).set(LAST_REGEXP_MATCH, match, isStrictContext());
- }
- return match;
+ return new NativeRegExpExecResult(match);
}
/**
@@ -744,15 +835,13 @@
* @return Index of match.
*/
Object search(final String string) {
- final RegExpMatch m = execInner(string);
- // the input string
- if (m == null) {
- setLastRegExpMatch(null);
+ final RegExpMatch match = execInner(string);
+
+ if (match == null) {
return -1;
}
- setLastRegExpMatch(new NativeRegExpExecResult(m));
- return m.getIndex();
+ return match.getIndex();
}
/**
@@ -780,10 +869,9 @@
}
private void init() {
- final ScriptObject proto = Global.instance().getRegExpPrototype();
- this.setProto(proto);
- // retrieve constructor to support "static" properties of RegExp
- this.constructor = PrototypeObject.getConstructor(proto);
+ // Keep reference to global object to support "static" properties of RegExp
+ this.globalObject = Global.instance();
+ this.setProto(globalObject.getRegExpPrototype());
}
private static NativeRegExp checkRegExp(final Object self) {
@@ -846,7 +934,4 @@
this.groupsInNegativeLookahead = groupsInNegativeLookahead;
}
- private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
- return MH.findStatic(MethodHandles.publicLookup(), NativeRegExp.class, name, MH.type(rtype, types));
- }
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/RegExpMatch.java Thu Feb 07 14:58:41 2013 +0100
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RegExpMatch.java Thu Feb 07 15:33:17 2013 +0100
@@ -77,4 +77,22 @@
public int length() {
return ((String)groups[0]).length();
}
+
+ /**
+ * Get the group with the given index or the empty string if group index is not valid.
+ * @param index the group index
+ * @return the group or ""
+ */
+ public Object getGroup(int index) {
+ return index >= 0 && index < groups.length ? groups[index] : "";
+ }
+
+ /**
+ * Get the last parenthesis group, or the empty string if none exists.
+ * @return the last group or ""
+ */
+ public Object getLastParen() {
+ return groups.length > 1 ? groups[groups.length - 1] : "";
+ }
+
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java Thu Feb 07 14:58:41 2013 +0100
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java Thu Feb 07 15:33:17 2013 +0100
@@ -89,10 +89,10 @@
public abstract class ScriptObject extends PropertyListenerManager implements PropertyAccess {
/** Search fall back routine name for "no such method" */
- public static final String NO_SUCH_METHOD_NAME = "__noSuchMethod__";
+ static final String NO_SUCH_METHOD_NAME = "__noSuchMethod__";
/** Search fall back routine name for "no such property" */
- public static final String NO_SUCH_PROPERTY_NAME = "__noSuchProperty__";
+ static final String NO_SUCH_PROPERTY_NAME = "__noSuchProperty__";
/** Per ScriptObject flag - is this a scope object? */
public static final int IS_SCOPE = 0b0000_0001;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8007718.js Thu Feb 07 15:33:17 2013 +0100
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8007718: Make static RegExp properties fully compatible to other engines
+ *
+ * @test
+ * @run
+ */
+
+function dumpRegExpProperties() {
+ for (var p in RegExp) {
+ print(p, RegExp[p], typeof RegExp[p]);
+ }
+}
+
+dumpRegExpProperties();
+
+/abc/.exec("xyz");
+dumpRegExpProperties();
+
+/(b)(a)(r)/gmi.exec("FOOBARfoo");
+dumpRegExpProperties();
+
+"abcdefghijklmnopq".match(/(\w)(\w)(\w)(\w)(\w)(\w)(\w)(\w)(\w)(\w)(\w)/);
+dumpRegExpProperties();
+
+"abcabcabcABCabcABC".split(/(b)/i);
+dumpRegExpProperties();
+
+"foobarfoo".search(/bar/);
+dumpRegExpProperties();
+
+/abc/.exec("xyz");
+dumpRegExpProperties();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8007718.js.EXPECTED Thu Feb 07 15:33:17 2013 +0100
@@ -0,0 +1,105 @@
+input string
+multiline false boolean
+lastMatch string
+lastParen string
+leftContext string
+rightContext string
+$1 string
+$2 string
+$3 string
+$4 string
+$5 string
+$6 string
+$7 string
+$8 string
+$9 string
+input string
+multiline false boolean
+lastMatch string
+lastParen string
+leftContext string
+rightContext string
+$1 string
+$2 string
+$3 string
+$4 string
+$5 string
+$6 string
+$7 string
+$8 string
+$9 string
+input FOOBARfoo string
+multiline false boolean
+lastMatch BAR string
+lastParen R string
+leftContext FOO string
+rightContext foo string
+$1 B string
+$2 A string
+$3 R string
+$4 string
+$5 string
+$6 string
+$7 string
+$8 string
+$9 string
+input abcdefghijklmnopq string
+multiline false boolean
+lastMatch abcdefghijk string
+lastParen k string
+leftContext string
+rightContext lmnopq string
+$1 a string
+$2 b string
+$3 c string
+$4 d string
+$5 e string
+$6 f string
+$7 g string
+$8 h string
+$9 i string
+input abcabcabcABCabcABC string
+multiline false boolean
+lastMatch B string
+lastParen B string
+leftContext abcabcabcABCabcA string
+rightContext C string
+$1 B string
+$2 string
+$3 string
+$4 string
+$5 string
+$6 string
+$7 string
+$8 string
+$9 string
+input foobarfoo string
+multiline false boolean
+lastMatch bar string
+lastParen string
+leftContext foo string
+rightContext foo string
+$1 string
+$2 string
+$3 string
+$4 string
+$5 string
+$6 string
+$7 string
+$8 string
+$9 string
+input foobarfoo string
+multiline false boolean
+lastMatch bar string
+lastParen string
+leftContext foo string
+rightContext foo string
+$1 string
+$2 string
+$3 string
+$4 string
+$5 string
+$6 string
+$7 string
+$8 string
+$9 string