--- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java 2014-12-08 15:58:14.329725884 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java 2014-12-08 15:58:14.209725886 +0100 @@ -465,10 +465,10 @@ // If this is either __FILE__, __DIR__, or __LINE__ then load the property initially as Object as we'd convert // it anyway for replaceLocationPropertyPlaceholder. if(identNode.isCompileTimePropertyName()) { - method.dynamicGet(Type.OBJECT, identNode.getSymbol().getName(), flags, identNode.isFunction()); + method.dynamicGet(Type.OBJECT, identNode.getSymbol().getName(), flags, identNode.isFunction(), false); replaceCompileTimeProperty(); } else { - dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction()); + dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction(), false); } } } @@ -486,7 +486,7 @@ private MethodEmitter storeFastScopeVar(final Symbol symbol, final int flags) { loadFastScopeProto(symbol, true); - method.dynamicSet(symbol.getName(), flags | CALLSITE_FAST_SCOPE); + method.dynamicSet(symbol.getName(), flags | CALLSITE_FAST_SCOPE, false); return method; } @@ -739,7 +739,7 @@ @Override void consumeStack() { final int flags = getCallSiteFlags(); - dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction()); + dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction(), accessNode.isIndex()); } }.emit(baseAlreadyOnStack ? 1 : 0); return false; @@ -1443,7 +1443,7 @@ // NOTE: not using a nested OptimisticOperation on this dynamicGet, as we expect to get back // a callable object. Nobody in their right mind would optimistically type this call site. assert !node.isOptimistic(); - method.dynamicGet(node.getType(), node.getProperty(), flags, true); + method.dynamicGet(node.getType(), node.getProperty(), flags, true, node.isIndex()); method.swap(); argCount = loadArgs(args); } @@ -3146,7 +3146,7 @@ if (isFastScope(identSymbol)) { storeFastScopeVar(identSymbol, flags); } else { - method.dynamicSet(identNode.getName(), flags); + method.dynamicSet(identNode.getName(), flags, false); } } else { final Type identType = identNode.getType(); @@ -4252,7 +4252,7 @@ if (isFastScope(symbol)) { storeFastScopeVar(symbol, flags); } else { - method.dynamicSet(node.getName(), flags); + method.dynamicSet(node.getName(), flags, false); } } else { final Type storeType = assignNode.getType(); @@ -4269,7 +4269,7 @@ @Override public boolean enterAccessNode(final AccessNode node) { - method.dynamicSet(node.getProperty(), getCallSiteFlags()); + method.dynamicSet(node.getProperty(), getCallSiteFlags(), node.isIndex()); return false; } @@ -4607,11 +4607,11 @@ * @param isMethod whether we're preferrably retrieving a function * @return the current method emitter */ - MethodEmitter dynamicGet(final String name, final int flags, final boolean isMethod) { + MethodEmitter dynamicGet(final String name, final int flags, final boolean isMethod, final boolean isIndex) { if(isOptimistic) { - return method.dynamicGet(getOptimisticCoercedType(), name, getOptimisticFlags(flags), isMethod); + return method.dynamicGet(getOptimisticCoercedType(), name, getOptimisticFlags(flags), isMethod, isIndex); } - return method.dynamicGet(resultBounds.within(expression.getType()), name, nonOptimisticFlags(flags), isMethod); + return method.dynamicGet(resultBounds.within(expression.getType()), name, nonOptimisticFlags(flags), isMethod, isIndex); } MethodEmitter dynamicGetIndex(final int flags, final boolean isMethod) { --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java 2014-12-08 15:58:14.961725873 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java 2014-12-08 15:58:14.841725875 +0100 @@ -34,6 +34,8 @@ import java.util.Collections; import java.util.List; import java.util.ListIterator; +import java.util.regex.Pattern; +import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BaseNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; @@ -52,6 +54,7 @@ import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; +import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.JumpStatement; import jdk.nashorn.internal.ir.LabelNode; import jdk.nashorn.internal.ir.LexicalContext; @@ -93,6 +96,10 @@ private final DebugLogger log; + // Conservative pattern to test if element names consist of characters valid for identifiers. + // This matches any non-zero length alphanumeric string including _ and $ and not starting with a digit. + private static Pattern SAFE_PROPERTY_NAME = Pattern.compile("[a-zA-Z_$][\\w$]*"); + /** * Constructor. */ @@ -140,7 +147,7 @@ } }); - this.log = initLogger(compiler.getContext()); + this.log = initLogger(compiler.getContext()); } @Override @@ -181,6 +188,31 @@ } @Override + public Node leaveIndexNode(final IndexNode indexNode) { + final String name = getConstantPropertyName(indexNode.getIndex()); + if (name != null) { + // If index node is a constant property name convert index node to access node. + assert Token.descType(indexNode.getToken()) == TokenType.LBRACKET; + return new AccessNode(indexNode.getToken(), indexNode.getFinish(), indexNode.getBase(), name); + } + return super.leaveIndexNode(indexNode); + } + + // If expression is a primitive literal that is not an array index and does return its string value. Else return null. + private static String getConstantPropertyName(final Expression expression) { + if (expression instanceof LiteralNode.PrimitiveLiteralNode) { + final LiteralNode literal = (LiteralNode) expression; + if (!literal.isNumeric()) { + final String str = literal.getString(); + if (SAFE_PROPERTY_NAME.matcher(str).matches()) { + return str; + } + } + } + return null; + } + + @Override public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) { final Expression expr = expressionStatement.getExpression(); ExpressionStatement node = expressionStatement; --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java 2014-12-08 15:58:15.469725864 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java 2014-12-08 15:58:15.333725866 +0100 @@ -2213,10 +2213,10 @@ * @param name name of property * @param flags call site flags * @param isMethod should it prefer retrieving methods - * + * @param isIndex is this an index operation? * @return the method emitter */ - MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod) { + MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod, final boolean isIndex) { if (name.length() > LARGE_STRING_THRESHOLD) { // use getIndex for extremely long names return load(name).dynamicGetIndex(valueType, flags, isMethod); } @@ -2229,8 +2229,8 @@ } popType(Type.SCOPE); - method.visitInvokeDynamicInsn((isMethod ? "dyn:getMethod|getProp|getElem:" : "dyn:getProp|getElem|getMethod:") + - NameCodec.encode(name), Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags); + method.visitInvokeDynamicInsn(dynGetOperation(isMethod, isIndex) + ':' + NameCodec.encode(name), + Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags); pushType(type); convert(valueType); //most probably a nop @@ -2243,8 +2243,9 @@ * * @param name name of property * @param flags call site flags + * @param isIndex is this an index operation? */ - void dynamicSet(final String name, final int flags) { + void dynamicSet(final String name, final int flags, final boolean isIndex) { if (name.length() > LARGE_STRING_THRESHOLD) { // use setIndex for extremely long names load(name).swap().dynamicSetIndex(flags); return; @@ -2261,7 +2262,8 @@ popType(type); popType(Type.SCOPE); - method.visitInvokeDynamicInsn("dyn:setProp|setElem:" + NameCodec.encode(name), methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags); + method.visitInvokeDynamicInsn(dynSetOperation(isIndex) + ':' + NameCodec.encode(name), + methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags); } /** @@ -2294,7 +2296,7 @@ final String signature = Type.getMethodDescriptor(resultType, Type.OBJECT /*e.g STRING->OBJECT*/, index); - method.visitInvokeDynamicInsn(isMethod ? "dyn:getMethod|getElem|getProp" : "dyn:getElem|getProp|getMethod", signature, LINKERBOOTSTRAP, flags); + method.visitInvokeDynamicInsn(dynGetOperation(isMethod, true), signature, LINKERBOOTSTRAP, flags); pushType(resultType); if (result.isBoolean()) { @@ -2508,6 +2510,18 @@ } } + private static String dynGetOperation(final boolean isMethod, final boolean isIndex) { + if (isMethod) { + return isIndex ? "dyn:getMethod|getElem|getProp" : "dyn:getMethod|getProp|getElem"; + } else { + return isIndex ? "dyn:getElem|getProp|getMethod" : "dyn:getProp|getElem|getMethod"; + } + } + + private static String dynSetOperation(final boolean isIndex) { + return isIndex ? "dyn:setElem|setProp" : "dyn:setProp|setElem"; + } + private Type emitLocalVariableConversion(final LocalVariableConversion conversion, final boolean onlySymbolLiveValue) { final Type from = conversion.getFrom(); final Type to = conversion.getTo(); --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java 2014-12-08 15:58:16.089725853 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SharedScopeCall.java 2014-12-08 15:58:15.933725856 +0100 @@ -156,7 +156,7 @@ assert !isCall || valueType.isObject(); // Callables are always objects // If flags are optimistic, but we're doing a call, remove optimistic flags from the getter, as they obviously // only apply to the call. - method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall); + method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall, false); // If this is a get we're done, otherwise call the value as function. if (isCall) { --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/AccessNode.java 2014-12-08 15:58:16.757725841 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/AccessNode.java 2014-12-08 15:58:16.613725844 +0100 @@ -28,6 +28,8 @@ import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.parser.Token; +import jdk.nashorn.internal.parser.TokenType; /** * IR representation of a property access (period operator.) @@ -101,6 +103,14 @@ return property; } + /** + * Return true if this node represents an index operation normally represented as {@link IndexNode}. + * @return true if an index access. + */ + public boolean isIndex() { + return Token.descType(getToken()) == TokenType.LBRACKET; + } + private AccessNode setBase(final Expression base) { if (this.base == base) { return this; --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java 2014-12-08 15:58:17.249725832 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java 2014-12-08 15:58:17.121725835 +0100 @@ -2001,12 +2001,11 @@ if (find == null) { switch (operator) { + case "getElem": // getElem only gets here if element name is constant, so treat it like a property access case "getProp": return noSuchProperty(desc, request); case "getMethod": return noSuchMethod(desc, request); - case "getElem": - return createEmptyGetter(desc, explicitInstanceOfCheck, name); default: throw new AssertionError(operator); // never invoked with any other operation } --- old/test/script/basic/list.js.EXPECTED 2014-12-08 15:58:17.881725821 +0100 +++ new/test/script/basic/list.js.EXPECTED 2014-12-08 15:58:17.749725824 +0100 @@ -10,7 +10,7 @@ l[0]=foo l[1]=a l[0.9]=null -l['blah']=null +l['blah']=undefined l[size_name]()=2 java.lang.IndexOutOfBoundsException: Index: 2, Size: 2 java.lang.IndexOutOfBoundsException: Index: Infinity, Size: 2