# HG changeset patch # User hannesw # Date 1360247597 -3600 # Node ID dff592a332a4323bdd3e1f5aab01ca92761244e1 # Parent 3e057d4357e091e9e3ea71f5f4daaea58aff3325 8007718: Make static RegExp properties fully compatible to other engines Reviewed-by: lagergren, sundar diff -r 3e057d4357e0 -r dff592a332a4 nashorn/src/jdk/nashorn/internal/objects/Global.java --- 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; + } + } diff -r 3e057d4357e0 -r dff592a332a4 nashorn/src/jdk/nashorn/internal/objects/NativeRegExp.java --- 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)); - } } diff -r 3e057d4357e0 -r dff592a332a4 nashorn/src/jdk/nashorn/internal/runtime/RegExpMatch.java --- 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] : ""; + } + } diff -r 3e057d4357e0 -r dff592a332a4 nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java --- 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; diff -r 3e057d4357e0 -r dff592a332a4 nashorn/test/script/basic/JDK-8007718.js --- /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(); diff -r 3e057d4357e0 -r dff592a332a4 nashorn/test/script/basic/JDK-8007718.js.EXPECTED --- /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