8007718: Make static RegExp properties fully compatible to other engines
authorhannesw
Thu, 07 Feb 2013 15:33:17 +0100
changeset 16223 dff592a332a4
parent 16222 3e057d4357e0
child 16224 0c49ad4e3b55
8007718: Make static RegExp properties fully compatible to other engines Reviewed-by: lagergren, sundar
nashorn/src/jdk/nashorn/internal/objects/Global.java
nashorn/src/jdk/nashorn/internal/objects/NativeRegExp.java
nashorn/src/jdk/nashorn/internal/runtime/RegExpMatch.java
nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java
nashorn/test/script/basic/JDK-8007718.js
nashorn/test/script/basic/JDK-8007718.js.EXPECTED
--- 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