diff options
author | Edward Nevill edward.nevill@linaro.org <Edward Nevill edward.nevill@linaro.org> | 2013-09-27 15:42:22 +0100 |
---|---|---|
committer | Edward Nevill edward.nevill@linaro.org <Edward Nevill edward.nevill@linaro.org> | 2013-09-27 15:42:22 +0100 |
commit | e93af65681ff4fb09c8b852c9a2a61e443be5f92 (patch) | |
tree | 53676a488a0ebe7e78c492066a4425d98aa382b6 | |
parent | 7a1be74c8dd1f0d9d3ca59049cad188d2a06241d (diff) | |
parent | 127e24f2037a33b97b35584fb3cff868aebe51c0 (diff) |
Merge up to jdk8-b90preview_rc1
227 files changed, 12600 insertions, 8506 deletions
@@ -195,3 +195,8 @@ b8a1b238c77c7c00024daaa2cb7d10838e017b5f jdk8-b69 053d7c55dc8272b58b8bb870dc92a4acf896d52a jdk8-b83 999cc1bf55203f51b2985feae6378932667ecff2 jdk8-b84 e0378f0a50dafdcfb7b04f6401d320f89884baa1 aarch64-20130813 +e0378f0a50dafdcfb7b04f6401d320f89884baa1 jdk8-b85 +002ad9d6735f36d1204e133324c73058c8abb1b0 jdk8-b86 +774aeaa89bc15f4365e3c2fc36f6a3a0da70ba28 jdk8-b87 +40c107d1ae6f81a62e35dfe618b827897405e9b2 jdk8-b88 +45ce27fbe2720d80070095c0db7344ec41e2833d jdk8-b89 diff --git a/bin/verbose_octane.sh b/bin/verbose_octane.sh index e70e9d48..1895afed 100644 --- a/bin/verbose_octane.sh +++ b/bin/verbose_octane.sh @@ -26,7 +26,7 @@ if [ -z $ITERS ]; then ITERS=7 fi NASHORN_JAR=dist/nashorn.jar -JVM_FLAGS="-XX:+UnlockDiagnosticVMOptions -Dnashorn.unstable.relink.threshold=8 -Xms2G -Xmx2G -XX:+TieredCompilation -server -jar ${NASHORN_JAR}" +JVM_FLAGS="-Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -XX:+UnlockDiagnosticVMOptions -Dnashorn.unstable.relink.threshold=8 -Xms2G -Xmx2G -XX:+TieredCompilation -server -jar ${NASHORN_JAR}" JVM_FLAGS7="-Xbootclasspath/p:${NASHORN_JAR} ${JVM_FLAGS}" OCTANE_ARGS="--verbose --iterations ${ITERS}" diff --git a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/ConstructorGenerator.java b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/ConstructorGenerator.java index 836cb893..7c59f8c3 100644 --- a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/ConstructorGenerator.java +++ b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/ConstructorGenerator.java @@ -38,7 +38,6 @@ import static jdk.nashorn.internal.tools.nasgen.StringConstants.MAP_DUPLICATE_DE import static jdk.nashorn.internal.tools.nasgen.StringConstants.MAP_FIELD_NAME; import static jdk.nashorn.internal.tools.nasgen.StringConstants.MAP_TYPE; import static jdk.nashorn.internal.tools.nasgen.StringConstants.OBJECT_DESC; -import static jdk.nashorn.internal.tools.nasgen.StringConstants.PROTOTYPE; import static jdk.nashorn.internal.tools.nasgen.StringConstants.PROTOTYPEOBJECT_SETCONSTRUCTOR; import static jdk.nashorn.internal.tools.nasgen.StringConstants.PROTOTYPEOBJECT_SETCONSTRUCTOR_DESC; import static jdk.nashorn.internal.tools.nasgen.StringConstants.PROTOTYPEOBJECT_TYPE; @@ -47,6 +46,8 @@ import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTFUNCTIONIM import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTFUNCTIONIMPL_TYPE; import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTFUNCTION_SETARITY; import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTFUNCTION_SETARITY_DESC; +import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTFUNCTION_SETPROTOTYPE; +import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTFUNCTION_SETPROTOTYPE_DESC; import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTFUNCTION_TYPE; import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTOBJECT_INIT_DESC; import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTOBJECT_TYPE; @@ -238,7 +239,7 @@ public class ConstructorGenerator extends ClassGenerator { mi.loadThis(); mi.invokeStatic(PROTOTYPEOBJECT_TYPE, PROTOTYPEOBJECT_SETCONSTRUCTOR, PROTOTYPEOBJECT_SETCONSTRUCTOR_DESC); - mi.putField(SCRIPTFUNCTION_TYPE, PROTOTYPE, OBJECT_DESC); + mi.invokeVirtual(SCRIPTFUNCTION_TYPE, SCRIPTFUNCTION_SETPROTOTYPE, SCRIPTFUNCTION_SETPROTOTYPE_DESC); } } diff --git a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/StringConstants.java b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/StringConstants.java index 8c2dcef0..5a5032f9 100644 --- a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/StringConstants.java +++ b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/StringConstants.java @@ -55,7 +55,6 @@ public interface StringConstants { static final Type TYPE_SCRIPTFUNCTIONIMPL = Type.getType(ScriptFunctionImpl.class); static final Type TYPE_SCRIPTOBJECT = Type.getType(ScriptObject.class); - static final String PROTOTYPE = "prototype"; static final String PROTOTYPE_SUFFIX = "$Prototype"; static final String CONSTRUCTOR_SUFFIX = "$Constructor"; // This field name is known to Nashorn runtime (Context). @@ -88,6 +87,8 @@ public interface StringConstants { Type.getMethodDescriptor(Type.VOID_TYPE, TYPE_STRING, TYPE_METHODHANDLE, TYPE_PROPERTYMAP, TYPE_METHODHANDLE_ARRAY); static final String SCRIPTFUNCTION_SETARITY = "setArity"; static final String SCRIPTFUNCTION_SETARITY_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE); + static final String SCRIPTFUNCTION_SETPROTOTYPE = "setPrototype"; + static final String SCRIPTFUNCTION_SETPROTOTYPE_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, TYPE_OBJECT); static final String PROTOTYPEOBJECT_TYPE = TYPE_PROTOTYPEOBJECT.getInternalName(); static final String PROTOTYPEOBJECT_SETCONSTRUCTOR = "setConstructor"; static final String PROTOTYPEOBJECT_SETCONSTRUCTOR_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, TYPE_OBJECT, TYPE_OBJECT); diff --git a/docs/JavaScriptingProgrammersGuide.html b/docs/JavaScriptingProgrammersGuide.html index dd243d3a..811b29c9 100644 --- a/docs/JavaScriptingProgrammersGuide.html +++ b/docs/JavaScriptingProgrammersGuide.html @@ -1,3 +1,27 @@ +<!-- + 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. Oracle designates this + particular file as subject to the "Classpath" exception as provided + by Oracle in the LICENSE file that accompanied this code. + + 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. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html class=" regenabled gecko radius jsenabled regloaded" xmlns="http://www.w3.org/1999/xhtml"><head> <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> diff --git a/make/build-nasgen.xml b/make/build-nasgen.xml index 5797befa..a50d41e0 100644 --- a/make/build-nasgen.xml +++ b/make/build-nasgen.xml @@ -37,6 +37,7 @@ <pathelement location="${basedir}/buildtools/nasgen/dist/nasgen.jar"/> <pathelement path="${basedir}/build/classes"/> </classpath> + <jvmarg value="-Djava.ext.dirs="/> <arg value="${basedir}/build/classes"/> <arg value="jdk.nashorn.internal.objects"/> <arg value="${basedir}/build/classes"/> diff --git a/make/build.xml b/make/build.xml index 945ccaa2..d884b2fe 100644 --- a/make/build.xml +++ b/make/build.xml @@ -56,7 +56,7 @@ <target name="init" depends="init-conditions, init-cc"> <!-- extends jvm args --> - <property name="run.test.jvmargs">${run.test.jvmargs.main} ${run.test.cc.jvmargs}</property> + <property name="run.test.jvmargs" value="${run.test.jvmargs.main} ${run.test.cc.jvmargs}"/> <property name="run.test.jvmargs.octane" value="${run.test.jvmargs.octane.main} ${run.test.cc.jvmargs}" /> <echo message="run.test.jvmargs=${run.test.jvmargs}"/> @@ -139,6 +139,31 @@ </manifest> </jar> </target> + + <target name="build-fxshell" depends="jar"> + <description>Builds the javafx shell.</description> + <mkdir dir="${fxshell.classes.dir}"/> + <javac srcdir="${fxshell.dir}" + destdir="${fxshell.classes.dir}" + classpath="${dist.jar}:${javac.classpath}" + debug="${javac.debug}" + encoding="${javac.encoding}" + includeantruntime="false"> + </javac> + <jar jarfile="${fxshell.jar}" manifest="${meta.inf.dir}/MANIFEST.MF" index="true" filesetmanifest="merge"> + <fileset dir="${fxshell.classes.dir}"/> + <manifest> + <attribute name="Archiver-Version" value="n/a"/> + <attribute name="Build-Jdk" value="${java.runtime.version}"/> + <attribute name="Built-By" value="n/a"/> + <attribute name="Created-By" value="Ant jar task"/> + <section name="jdk/nashorn/"> + <attribute name="Implementation-Title" value="Oracle Nashorn FXShell"/> + <attribute name="Implementation-Version" value="${nashorn.version}"/> + </section> + </manifest> + </jar> + </target> <target name="javadoc" depends="prepare"> <javadoc destdir="${dist.javadoc.dir}" use="yes" overview="src/overview.html" windowtitle="${nashorn.product.name} ${nashorn.version}" additionalparam="-quiet" failonerror="true"> diff --git a/make/project.properties b/make/project.properties index 58da977d..e2f39bb7 100644 --- a/make/project.properties +++ b/make/project.properties @@ -65,6 +65,12 @@ dist.dir=dist dist.jar=${dist.dir}/nashorn.jar dist.javadoc.dir=${dist.dir}/javadoc +# nashorn javafx shell +fxshell.tool = jdk.nashorn.tools.FXShell +fxshell.classes.dir = ${build.dir}/fxshell/classes +fxshell.dir = tools/fxshell +fxshell.jar = ${dist.dir}/nashornfx.jar + # jars refererred file.reference.testng.jar=test/lib/testng.jar @@ -208,7 +214,7 @@ run.test.xms=2G # -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMethods # add '-Dtest.js.outofprocess' to run each test in a new sub-process -run.test.jvmargs.main=-server -Xmx${run.test.xmx} -XX:-TieredCompilation -esa -ea -Dnashorn.debug=true -Dfile.encoding=UTF-8 +run.test.jvmargs.main=-server -Xmx${run.test.xmx} -XX:-TieredCompilation -ea -Dnashorn.debug=true -Dfile.encoding=UTF-8 #-XX:+HeapDumpOnOutOfMemoryError -XX:-UseCompressedKlassPointers -XX:+PrintHeapAtGC -XX:ClassMetaspaceSize=300M run.test.jvmargs.octane.main=-Xms${run.test.xms} ${run.test.jvmargs.main} diff --git a/src/jdk/internal/dynalink/beans/ClassString.java b/src/jdk/internal/dynalink/beans/ClassString.java index d6c6da08..dfcb3786 100644 --- a/src/jdk/internal/dynalink/beans/ClassString.java +++ b/src/jdk/internal/dynalink/beans/ClassString.java @@ -96,6 +96,11 @@ import jdk.internal.dynalink.support.TypeUtilities; * @author Attila Szegedi */ final class ClassString { + /** + * An anonymous inner class used solely to represent the "type" of null values for method applicability checking. + */ + static final Class<?> NULL_CLASS = (new Object() { /* Intentionally empty */ }).getClass(); + private final Class<?>[] classes; private int hashCode; @@ -203,6 +208,9 @@ final class ClassString { } private static boolean canConvert(LinkerServices ls, Class<?> from, Class<?> to) { + if(from == NULL_CLASS) { + return !to.isPrimitive(); + } return ls == null ? TypeUtilities.isMethodInvocationConvertible(from, to) : ls.canConvert(from, to); } } diff --git a/src/jdk/internal/dynalink/beans/OverloadedMethod.java b/src/jdk/internal/dynalink/beans/OverloadedMethod.java index d0015162..7093e757 100644 --- a/src/jdk/internal/dynalink/beans/OverloadedMethod.java +++ b/src/jdk/internal/dynalink/beans/OverloadedMethod.java @@ -152,7 +152,7 @@ class OverloadedMethod { final Class<?>[] argTypes = new Class[args.length]; for(int i = 0; i < argTypes.length; ++i) { final Object arg = args[i]; - argTypes[i] = arg == null ? callSiteType.parameterType(i) : arg.getClass(); + argTypes[i] = arg == null ? ClassString.NULL_CLASS : arg.getClass(); } final ClassString classString = new ClassString(argTypes); MethodHandle method = argTypesToMethods.get(classString); diff --git a/src/jdk/internal/dynalink/beans/StaticClassIntrospector.java b/src/jdk/internal/dynalink/beans/StaticClassIntrospector.java index d4cedb7f..214152a4 100644 --- a/src/jdk/internal/dynalink/beans/StaticClassIntrospector.java +++ b/src/jdk/internal/dynalink/beans/StaticClassIntrospector.java @@ -106,7 +106,11 @@ class StaticClassIntrospector extends FacetIntrospector { @Override MethodHandle editMethodHandle(MethodHandle mh) { - MethodHandle newHandle = MethodHandles.dropArguments(mh, 0, Object.class); + return dropReceiver(mh, Object.class); + } + + static MethodHandle dropReceiver(final MethodHandle mh, final Class<?> receiverClass) { + MethodHandle newHandle = MethodHandles.dropArguments(mh, 0, receiverClass); // NOTE: this is a workaround for the fact that dropArguments doesn't preserve vararg collector state. if(mh.isVarargsCollector() && !newHandle.isVarargsCollector()) { final MethodType type = mh.type(); diff --git a/src/jdk/internal/dynalink/beans/StaticClassLinker.java b/src/jdk/internal/dynalink/beans/StaticClassLinker.java index c87d6667..d6096fe5 100644 --- a/src/jdk/internal/dynalink/beans/StaticClassLinker.java +++ b/src/jdk/internal/dynalink/beans/StaticClassLinker.java @@ -144,7 +144,7 @@ class StaticClassLinker implements TypeBasedGuardingDynamicLinker { } private static MethodHandle drop(MethodHandle mh) { - return MethodHandles.dropArguments(mh, 0, StaticClass.class); + return StaticClassIntrospector.dropReceiver(mh, StaticClass.class); } @Override diff --git a/src/jdk/nashorn/api/scripting/Formatter.java b/src/jdk/nashorn/api/scripting/Formatter.java index 5cb19ed4..a14a83e4 100644 --- a/src/jdk/nashorn/api/scripting/Formatter.java +++ b/src/jdk/nashorn/api/scripting/Formatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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 diff --git a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java index 55967bb0..197a6da4 100644 --- a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java +++ b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java @@ -397,10 +397,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C } setContextVariables(ctxt); - final Object val = ctxt.getAttribute(ScriptEngine.FILENAME); - final String fileName = (val != null) ? val.toString() : "<eval>"; - Object res = ScriptRuntime.apply(script, ctxtGlobal); - return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(res, ctxtGlobal)); + return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); } catch (final Exception e) { throwAsScriptException(e); throw new AssertionError("should not reach here"); diff --git a/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java b/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java index e38284da..1ca6dcdd 100644 --- a/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java +++ b/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java @@ -210,42 +210,10 @@ public final class NashornScriptEngineFactory implements ScriptEngineFactory { } private static ClassLoader getAppClassLoader() { - if (System.getSecurityManager() == null) { - return Thread.currentThread().getContextClassLoader(); - } - - // Try to determine the caller class loader. Use that if it can be - // found. If not, use the class loader of nashorn itself as the - // "application" class loader for scripts. - - // User could have called ScriptEngineFactory.getScriptEngine() - // - // <caller> - // <factory.getScriptEngine()> - // <factory.getAppClassLoader()> - // <Reflection.getCallerClass()> - // - // or used one of the getEngineByABC methods of ScriptEngineManager. - // - // <caller> - // <ScriptEngineManager.getEngineByName()> - // <factory.getScriptEngine()> - // <factory.getAppClassLoader()> - // <Reflection.getCallerClass()> - - // So, stack depth is 3 or 4 (recall it is zero based). We try - // stack depths 3, 4 and look for non-bootstrap caller. - Class<?> caller = null; - for (int depth = 3; depth < 5; depth++) { - caller = Reflection.getCallerClass(depth); - if (caller != null && caller.getClassLoader() != null) { - // found a non-bootstrap caller - break; - } - } - - final ClassLoader ccl = (caller == null)? null : caller.getClassLoader(); - // if caller loader is null, then use nashorn's own loader + // Revisit: script engine implementation needs the capability to + // find the class loader of the context in which the script engine + // is running so that classes will be found and loaded properly + ClassLoader ccl = Thread.currentThread().getContextClassLoader(); return (ccl == null)? NashornScriptEngineFactory.class.getClassLoader() : ccl; } } diff --git a/src/jdk/nashorn/internal/codegen/Attr.java b/src/jdk/nashorn/internal/codegen/Attr.java index 9ecf7c89..9b17c47f 100644 --- a/src/jdk/nashorn/internal/codegen/Attr.java +++ b/src/jdk/nashorn/internal/codegen/Attr.java @@ -25,14 +25,16 @@ package jdk.nashorn.internal.codegen; +import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; import static jdk.nashorn.internal.codegen.CompilerConstants.EXCEPTION_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.ITERATOR_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; -import static jdk.nashorn.internal.codegen.CompilerConstants.SCRIPT_RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.SWITCH_TAG_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; +import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF; import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL; import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; import static jdk.nashorn.internal.ir.Symbol.IS_LET; @@ -42,18 +44,18 @@ import static jdk.nashorn.internal.ir.Symbol.IS_THIS; import static jdk.nashorn.internal.ir.Symbol.IS_VAR; import static jdk.nashorn.internal.ir.Symbol.KINDMASK; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Set; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.CallNode; -import jdk.nashorn.internal.ir.CallNode.EvalArgs; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ForNode; @@ -62,6 +64,7 @@ import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.LexicalContext; +import jdk.nashorn.internal.ir.LexicalContextNode; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; import jdk.nashorn.internal.ir.Node; @@ -76,6 +79,7 @@ import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.TryNode; import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.VarNode; +import jdk.nashorn.internal.ir.WithNode; import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.parser.TokenType; @@ -105,21 +109,22 @@ import jdk.nashorn.internal.runtime.ScriptObject; */ final class Attr extends NodeOperatorVisitor { + /** * Local definitions in current block (to discriminate from function * declarations always defined in the function scope. This is for * "can be undefined" analysis. */ - private Set<String> localDefs; + private final Deque<Set<String>> localDefs; /** * Local definitions in current block to guard against cases like * NASHORN-467 when things can be undefined as they are used before * their local var definition. *sigh* JavaScript... */ - private Set<String> localUses; + private final Deque<Set<String>> localUses; - private final LexicalContext lexicalContext = new LexicalContext(); + private final Deque<Type> returnTypes; private static final DebugLogger LOG = new DebugLogger("attr"); private static final boolean DEBUG = LOG.isEnabled(); @@ -128,10 +133,13 @@ final class Attr extends NodeOperatorVisitor { * Constructor. */ Attr() { + localDefs = new ArrayDeque<>(); + localUses = new ArrayDeque<>(); + returnTypes = new ArrayDeque<>(); } @Override - protected Node enterDefault(final Node node) { + protected boolean enterDefault(final Node node) { return start(node); } @@ -142,76 +150,127 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveAccessNode(final AccessNode accessNode) { - newTemporary(Type.OBJECT, accessNode); //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this + ensureSymbol(Type.OBJECT, accessNode); //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this end(accessNode); return accessNode; } - @Override - public Node enterBlock(final Block block) { - lexicalContext.push(block); - start(block); + private void enterFunctionBody() { - final Set<String> savedLocalDefs = localDefs; - final Set<String> savedLocalUses = localUses; + final FunctionNode functionNode = getLexicalContext().getCurrentFunction(); + final Block body = getLexicalContext().getCurrentBlock(); + initCallee(body); + initThis(body); + if (functionNode.isVarArg()) { + initVarArg(body, functionNode.needsArguments()); + } - block.setFrame(getCurrentFunctionNode().pushFrame()); + initParameters(functionNode, body); + initScope(body); + initReturn(body); - try { - // a block starts out by copying the local defs and local uses - // from the outer level. But we need the copies, as when we - // leave the block the def and use sets given upon entry must - // be restored - localDefs = new HashSet<>(savedLocalDefs); - localUses = new HashSet<>(savedLocalUses); - - block.visitStatements(this); - } finally { - localDefs = savedLocalDefs; - localUses = savedLocalUses; + if (functionNode.isProgram()) { + initFromPropertyMap(body); + } else if(!functionNode.isDeclared()) { + // It's neither declared nor program - it's a function expression then; assign it a self-symbol. - getCurrentFunctionNode().popFrame(); + if (functionNode.getSymbol() != null) { + // a temporary left over from an earlier pass when the function was lazy + assert functionNode.getSymbol().isTemp(); + // remove it + functionNode.setSymbol(null); + } + final boolean anonymous = functionNode.isAnonymous(); + final String name = anonymous ? null : functionNode.getIdent().getName(); + if (anonymous || body.getExistingSymbol(name) != null) { + // The function is either anonymous, or another local identifier already trumps its name on entry: + // either it has the same name as one of its parameters, or is named "arguments" and also references the + // "arguments" identifier in its body. + ensureSymbol(functionNode, Type.typeFor(ScriptFunction.class), functionNode); + } else { + final Symbol selfSymbol = defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF, functionNode); + assert selfSymbol.isFunctionSelf(); + newType(selfSymbol, Type.OBJECT); + } } - end(block); + /* + * This pushes all declarations (except for non-statements, i.e. for + * node temporaries) to the top of the function scope. This way we can + * get around problems like + * + * while (true) { + * break; + * if (true) { + * var s; + * } + * } + * + * to an arbitrary nesting depth. + * + * @see NASHORN-73 + */ - lexicalContext.pop(block); - return null; - } + // This visitor will assign symbol to all declared variables, except function declarations (which are taken care + // in a separate step above) and "var" declarations in for loop initializers. + body.accept(new NodeOperatorVisitor() { + @Override + public boolean enterFunctionNode(final FunctionNode nestedFn) { + return false; + } - @Override - public Node enterCallNode(final CallNode callNode) { - start(callNode); + @Override + public boolean enterVarNode(final VarNode varNode) { - callNode.getFunction().accept(this); + // any declared symbols that aren't visited need to be typed as well, hence the list - final List<Node> acceptedArgs = new ArrayList<>(callNode.getArgs().size()); - for (final Node arg : callNode.getArgs()) { - LOG.info("Doing call arg " + arg); - acceptedArgs.add(arg.accept(this)); - } - callNode.setArgs(acceptedArgs); + if (varNode.isStatement()) { - final EvalArgs evalArgs = callNode.getEvalArgs(); - if (evalArgs != null) { - evalArgs.setCode(evalArgs.getCode().accept(this)); + final IdentNode ident = varNode.getName(); + final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR, new IdentNode(ident)); + functionNode.addDeclaredSymbol(symbol); + if (varNode.isFunctionDeclaration()) { + newType(symbol, FunctionNode.FUNCTION_TYPE); + } + } + return false; + } + }); + } - final IdentNode thisNode = new IdentNode(getCurrentFunctionNode().getThisNode()); - assert thisNode.getSymbol() != null; //should copy attributed symbol and that's it - evalArgs.setThis(thisNode); + @Override + public boolean enterBlock(final Block block) { + start(block); + + if (getLexicalContext().isFunctionBody()) { + enterFunctionBody(); } + pushLocalsBlock(); - newTemporary(callNode.getType(), callNode); // access specialization in FinalizeTypes may narrow it further later + return true; + } - end(callNode); + @Override + public Node leaveBlock(final Block block) { + popLocals(); + return end(block); + } - return null; + @Override + public Node leaveCallNode(final CallNode callNode) { + ensureSymbol(callNode.getType(), callNode); + return end(callNode); } @Override - public Node enterCatchNode(final CatchNode catchNode) { + public boolean enterCallNode(final CallNode callNode) { + return start(callNode); + } + + @Override + public boolean enterCatchNode(final CatchNode catchNode) { final IdentNode exception = catchNode.getException(); - final Block block = getCurrentBlock(); + final Block block = getLexicalContext().getCurrentBlock(); start(catchNode); @@ -220,7 +279,7 @@ final class Attr extends NodeOperatorVisitor { newType(def, Type.OBJECT); addLocalDef(exception.getName()); - return catchNode; + return true; } /** @@ -240,7 +299,7 @@ final class Attr extends NodeOperatorVisitor { flags |= IS_SCOPE; } - final FunctionNode function = lexicalContext.getFunction(block); + final FunctionNode function = getLexicalContext().getFunction(block); if (symbol != null) { // Symbol was already defined. Check if it needs to be redefined. if ((flags & KINDMASK) == IS_PARAM) { @@ -254,7 +313,6 @@ final class Attr extends NodeOperatorVisitor { } } else if ((flags & KINDMASK) == IS_VAR) { if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) { - assert !((flags & IS_LET) == IS_LET && symbol.getBlock() == block) : "duplicate let variable in block"; // Always create a new definition. symbol = null; } else { @@ -272,17 +330,16 @@ final class Attr extends NodeOperatorVisitor { // Determine where to create it. if ((flags & Symbol.KINDMASK) == IS_VAR && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) { - symbolBlock = block; + symbolBlock = block; //internal vars are always defined in the block closest to them } else { - symbolBlock = function; + symbolBlock = getLexicalContext().getFunctionBody(function); } // Create and add to appropriate block. - symbol = new Symbol(name, flags, node, symbolBlock); + symbol = new Symbol(name, flags); symbolBlock.putSymbol(name, symbol); if ((flags & Symbol.KINDMASK) != IS_GLOBAL) { - symbolBlock.getFrame().addSymbol(symbol); symbol.setNeedsSlot(true); } } else if (symbol.less(flags)) { @@ -297,149 +354,100 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterFunctionNode(final FunctionNode functionNode) { + public boolean enterFunctionNode(final FunctionNode functionNode) { start(functionNode, false); - if (functionNode.isLazy()) { - LOG.info("LAZY: " + functionNode.getName() + " => Promoting to OBJECT"); - newTemporary(lexicalContext.getCurrentFunction(), Type.OBJECT, functionNode); - functionNode.setReturnType(Type.OBJECT); - end(functionNode); - return null; - } - - lexicalContext.push(functionNode); - - clearLocalDefs(); - clearLocalUses(); - - functionNode.setFrame(functionNode.pushFrame()); - - initCallee(functionNode); - initThis(functionNode); - if (functionNode.isVarArg()) { - initVarArg(functionNode); - } - initParameters(functionNode); - initScope(functionNode); - initReturn(functionNode); - - // Add all nested declared functions as symbols in this function - for (final FunctionNode nestedFunction : functionNode.getDeclaredFunctions()) { - final IdentNode ident = nestedFunction.getIdent(); - if (ident != null) { - assert nestedFunction.isDeclared(); - final Symbol functionSymbol = defineSymbol(functionNode, ident.getName(), IS_VAR, nestedFunction); - newType(functionSymbol, Type.typeFor(ScriptFunction.class)); + if (functionNode.isDeclared()) { + final Iterator<Block> blocks = getLexicalContext().getBlocks(); + if (blocks.hasNext()) { + defineSymbol( + blocks.next(), + functionNode.getIdent().getName(), + IS_VAR, + functionNode); + } else { + // Q: What's an outermost function in a lexical context that is not a program? + // A: It's a function being compiled lazily! + assert getLexicalContext().getOutermostFunction() == functionNode && !functionNode.isProgram(); } } - if (functionNode.isProgram()) { - initFromPropertyMap(functionNode); - } - - // Add function name as local symbol - if (!functionNode.isDeclared() && !functionNode.isProgram()) { - if(functionNode.getSymbol() != null) { - // a temporary left over from an earlier pass when the function was lazy - assert functionNode.getSymbol().isTemp(); - // remove it - functionNode.setSymbol(null); - } - final Symbol selfSymbol; - if(functionNode.isAnonymous()) { - selfSymbol = newTemporary(functionNode, Type.OBJECT, functionNode); - } else { - selfSymbol = defineSymbol(functionNode, functionNode.getIdent().getName(), IS_VAR, functionNode); - newType(selfSymbol, Type.OBJECT); - selfSymbol.setNode(functionNode); - } + if (functionNode.isLazy()) { + LOG.info("LAZY: ", functionNode.getName(), " => Promoting to OBJECT"); + ensureSymbol(getLexicalContext().getCurrentFunction(), Type.OBJECT, functionNode); + end(functionNode); + return false; } - /* - * This pushes all declarations (except for non-statements, i.e. for - * node temporaries) to the top of the function scope. This way we can - * get around problems like - * - * while (true) { - * break; - * if (true) { - * var s; - * } - * } - * - * to an arbitrary nesting depth. - * - * @see NASHORN-73 - */ + returnTypes.push(functionNode.getReturnType()); + pushLocalsFunction(); + return true; + } - final List<Symbol> declaredSymbols = new ArrayList<>(); - // This visitor will assign symbol to all declared variables, except function declarations (which are taken care - // in a separate step above) and "var" declarations in for loop initializers. - functionNode.accept(new NodeOperatorVisitor() { - @Override - public Node enterFunctionNode(FunctionNode nestedFn) { - // Don't descend into nested functions - return nestedFn == functionNode ? nestedFn : null; - } - @Override - public Node enterVarNode(VarNode varNode) { - if(varNode.isStatement() && !varNode.isFunctionDeclaration()) { - final IdentNode ident = varNode.getName(); - // any declared symbols that aren't visited need to be typed as well, hence the list - declaredSymbols.add(defineSymbol(functionNode, ident.getName(), IS_VAR, new IdentNode(ident))); - } - return null; - } - }); + @Override + public Node leaveFunctionNode(final FunctionNode functionNode) { + FunctionNode newFunctionNode = functionNode; - visitFunctionStatements(functionNode); + final LexicalContext lc = getLexicalContext(); //unknown parameters are promoted to object type. - finalizeParameters(functionNode); - finalizeTypes(functionNode); - for (final Symbol symbol : declaredSymbols) { + finalizeParameters(newFunctionNode); + finalizeTypes(newFunctionNode); + for (final Symbol symbol : newFunctionNode.getDeclaredSymbols()) { if (symbol.getSymbolType().isUnknown()) { symbol.setType(Type.OBJECT); symbol.setCanBeUndefined(); } } - if (functionNode.getReturnType().isUnknown()) { - LOG.info("Unknown return type promoted to object"); - functionNode.setReturnType(Type.OBJECT); - } + final Block body = newFunctionNode.getBody(); - if (functionNode.getSelfSymbolInit() != null) { - LOG.info("Accepting self symbol init " + functionNode.getSelfSymbolInit() + " for " + functionNode.getName()); - final Node init = functionNode.getSelfSymbolInit(); - final List<Node> newStatements = new ArrayList<>(); - newStatements.add(init); - newStatements.addAll(functionNode.getStatements()); - functionNode.setStatements(newStatements); - functionNode.setNeedsSelfSymbol(functionNode.getSelfSymbolInit().accept(this)); + if (newFunctionNode.hasLazyChildren()) { + //the final body has already been assigned as we have left the function node block body by now + objectifySymbols(body); } - if (functionNode.hasLazyChildren()) { - objectifySymbols(functionNode); - } + if (body.getFlag(Block.NEEDS_SELF_SYMBOL)) { + final IdentNode callee = compilerConstant(CALLEE); + final VarNode selfInit = + new VarNode( + newFunctionNode.getSource(), + newFunctionNode.getToken(), + newFunctionNode.getFinish(), + newFunctionNode.getIdent(), + callee); - functionNode.popFrame(); + LOG.info("Accepting self symbol init ", selfInit, " for ", newFunctionNode.getName()); - functionNode.setState(CompilationState.ATTR); + final List<Node> newStatements = new ArrayList<>(); + newStatements.add(selfInit); + assert callee.getSymbol() != null && callee.getSymbol().hasSlot(); - end(functionNode, false); - lexicalContext.pop(functionNode); + final IdentNode name = selfInit.getName(); + final Symbol nameSymbol = body.getExistingSymbol(name.getName()); - return null; - } + assert nameSymbol != null; + + name.setSymbol(nameSymbol); + selfInit.setSymbol(nameSymbol); - private void visitFunctionStatements(final FunctionNode functionNode) { - final List<Node> newStatements = new ArrayList<>(functionNode.getStatements()); - for(ListIterator<Node> stmts = newStatements.listIterator(); stmts.hasNext();) { - stmts.set(stmts.next().accept(this)); + newStatements.addAll(body.getStatements()); + newFunctionNode = newFunctionNode.setBody(lc, body.setStatements(lc, newStatements)); + } + + if (returnTypes.peek().isUnknown()) { + LOG.info("Unknown return type promoted to object"); + newFunctionNode = newFunctionNode.setReturnType(lc, Type.OBJECT); } - functionNode.setStatements(newStatements); + final Type returnType = returnTypes.pop(); + newFunctionNode = newFunctionNode.setReturnType(lc, returnType.isUnknown() ? Type.OBJECT : returnType); + newFunctionNode = newFunctionNode.setState(lc, CompilationState.ATTR); + + popLocals(); + + end(newFunctionNode, false); + + return newFunctionNode; //.setFlag(lc, lc.getFlags(functionNode)); } @Override @@ -450,7 +458,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterIdentNode(final IdentNode identNode) { + public boolean enterIdentNode(final IdentNode identNode) { final String name = identNode.getName(); start(identNode); @@ -458,31 +466,28 @@ final class Attr extends NodeOperatorVisitor { if (identNode.isPropertyName()) { // assign a pseudo symbol to property name final Symbol pseudoSymbol = pseudoSymbol(name); - LOG.info("IdentNode is property name -> assigning pseudo symbol " + pseudoSymbol); + LOG.info("IdentNode is property name -> assigning pseudo symbol ", pseudoSymbol); LOG.unindent(); identNode.setSymbol(pseudoSymbol); - return null; + return false; } - final Block block = getCurrentBlock(); - final Symbol oldSymbol = identNode.getSymbol(); + final LexicalContext lc = getLexicalContext(); + final Block block = lc.getCurrentBlock(); + final Symbol oldSymbol = identNode.getSymbol(); Symbol symbol = findSymbol(block, name); //If an existing symbol with the name is found, use that otherwise, declare a new one if (symbol != null) { - LOG.info("Existing symbol = " + symbol); - if (isFunctionExpressionSelfReference(symbol)) { - final FunctionNode functionNode = (FunctionNode)symbol.getNode(); - assert functionNode.getCalleeNode() != null; - - final VarNode var = new VarNode(functionNode.getSource(), functionNode.getToken(), functionNode.getFinish(), functionNode.getIdent(), functionNode.getCalleeNode()); - //newTemporary(Type.OBJECT, var); //ScriptFunction? TODO - - functionNode.setNeedsSelfSymbol(var); - } - - if (!identNode.isInitializedHere()) { // NASHORN-448 + LOG.info("Existing symbol = ", symbol); + if (symbol.isFunctionSelf()) { + final FunctionNode functionNode = lc.getDefiningFunction(symbol); + assert functionNode != null; + assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null; + lc.setFlag(functionNode.getBody(), Block.NEEDS_SELF_SYMBOL); + newType(symbol, FunctionNode.FUNCTION_TYPE); + } else if (!identNode.isInitializedHere()) { // NASHORN-448 // here is a use outside the local def scope if (!isLocalDef(name)) { newType(symbol, Type.OBJECT); @@ -491,26 +496,18 @@ final class Attr extends NodeOperatorVisitor { } identNode.setSymbol(symbol); - // non-local: we need to put symbol in scope (if it isn't already) - if (!isLocal(getCurrentFunctionNode(), symbol) && !symbol.isScope()) { - symbol.setIsScope(); - } + // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already) + maybeForceScope(symbol); } else { - LOG.info("No symbol exists. Declare undefined: " + symbol); - symbol = useSymbol(block, name, identNode); + LOG.info("No symbol exists. Declare undefined: ", symbol); + symbol = defineSymbol(block, name, IS_GLOBAL, identNode); // we have never seen this before, it can be undefined newType(symbol, Type.OBJECT); // TODO unknown -we have explicit casts anyway? symbol.setCanBeUndefined(); - symbol.setIsScope(); + Symbol.setSymbolIsScope(lc, symbol); } - assert symbol != null; - if(symbol.isGlobal()) { - setUsesGlobalSymbol(); - } else if(symbol.isScope()) { - final Iterator<Block> blocks = lexicalContext.getBlocks(); - blocks.next().setUsesScopeSymbol(symbol, blocks); - } + setBlockScope(name, symbol); if (symbol != oldSymbol && !identNode.isInitializedHere()) { symbol.increaseUseCount(); @@ -519,42 +516,93 @@ final class Attr extends NodeOperatorVisitor { end(identNode); - return null; + return false; } /** - * Marks the current function as one using any global symbol. The function and all its parent functions will all be - * marked as needing parent scope. - * @see #needsParentScope() + * If the symbol isn't already a scope symbol, and it is either not local to the current function, or it is being + * referenced from within a with block, we force it to be a scope symbol. + * @param symbol the symbol that might be scoped */ - private void setUsesGlobalSymbol() { - for(final Iterator<FunctionNode> fns = lexicalContext.getFunctions(); fns.hasNext();) { - fns.next().setUsesAncestorScope(); + private void maybeForceScope(final Symbol symbol) { + if(!symbol.isScope() && symbolNeedsToBeScope(symbol)) { + Symbol.setSymbolIsScope(getLexicalContext(), symbol); } } - /** - * Declare the use of a symbol in a block. - * - * @param block block in which the symbol is used - * @param name Name of symbol. - * @param node Using node - * - * @return Symbol for given name. - */ - private Symbol useSymbol(final Block block, final String name, final Node node) { - Symbol symbol = findSymbol(block, name); + private boolean symbolNeedsToBeScope(Symbol symbol) { + if(symbol.isThis() || symbol.isInternal()) { + return false; + } + boolean previousWasBlock = false; + for(final Iterator<LexicalContextNode> it = getLexicalContext().getAllNodes(); it.hasNext();) { + final LexicalContextNode node = it.next(); + if(node instanceof FunctionNode) { + // We reached the function boundary without seeing a definition for the symbol - it needs to be in + // scope. + return true; + } else if(node instanceof WithNode) { + if(previousWasBlock) { + // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately + // preceded by a block, this means we're currently processing its expression, not its body, + // therefore it doesn't count. + return true; + } + previousWasBlock = false; + } else if(node instanceof Block) { + if(((Block)node).getExistingSymbol(symbol.getName()) == symbol) { + // We reached the block that defines the symbol without reaching either the function boundary, or a + // WithNode. The symbol need not be scoped. + return false; + } + previousWasBlock = true; + } else { + previousWasBlock = false; + } + } + throw new AssertionError(); + } - if (symbol == null) { - // If not found, declare as a free var. - symbol = defineSymbol(block, name, IS_GLOBAL, node); - } else { - node.setSymbol(symbol); + private void setBlockScope(final String name, final Symbol symbol) { + assert symbol != null; + if (symbol.isGlobal()) { + setUsesGlobalSymbol(); + return; } - return symbol; + if (symbol.isScope()) { + final LexicalContext lc = getLexicalContext(); + + Block scopeBlock = null; + for (final Iterator<LexicalContextNode> contextNodeIter = getLexicalContext().getAllNodes(); contextNodeIter.hasNext(); ) { + final LexicalContextNode node = contextNodeIter.next(); + if (node instanceof Block) { + if (((Block)node).getExistingSymbol(name) != null) { + scopeBlock = (Block)node; + break; + } + } else if (node instanceof FunctionNode) { + lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE); + } + } + + if (scopeBlock != null) { + assert getLexicalContext().contains(scopeBlock); + lc.setFlag(scopeBlock, Block.NEEDS_SCOPE); + } + } } + /** + * Marks the current function as one using any global symbol. The function and all its parent functions will all be + * marked as needing parent scope. + * @see #needsParentScope() + */ + private void setUsesGlobalSymbol() { + for (final Iterator<FunctionNode> fns = getLexicalContext().getFunctions(); fns.hasNext();) { + getLexicalContext().setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE); + } + } /** * Search for symbol in the lexical context starting from the given block. @@ -564,7 +612,7 @@ final class Attr extends NodeOperatorVisitor { private Symbol findSymbol(final Block block, final String name) { // Search up block chain to locate symbol. - for(final Iterator<Block> blocks = lexicalContext.getBlocks(block); blocks.hasNext();) { + for(final Iterator<Block> blocks = getLexicalContext().getBlocks(block); blocks.hasNext();) { // Find name. final Symbol symbol = blocks.next().getExistingSymbol(name); // If found then we are good. @@ -577,13 +625,13 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveIndexNode(final IndexNode indexNode) { - newTemporary(Type.OBJECT, indexNode); //TODO + ensureSymbol(Type.OBJECT, indexNode); //TODO return indexNode; } @SuppressWarnings("rawtypes") @Override - public Node enterLiteralNode(final LiteralNode literalNode) { + public boolean enterLiteralNode(final LiteralNode literalNode) { try { start(literalNode); assert !literalNode.isTokenType(TokenType.THIS) : "tokentype for " + literalNode + " is this"; //guard against old dead code case. literal nodes should never inherit tokens @@ -604,26 +652,33 @@ final class Attr extends NodeOperatorVisitor { assert !(literalNode.getValue() instanceof Node) : "literals with Node values not supported"; } - getCurrentFunctionNode().newLiteral(literalNode); + getLexicalContext().getCurrentFunction().newLiteral(literalNode); } finally { end(literalNode); } - return null; + + return false; + } + + @Override + public boolean enterObjectNode(final ObjectNode objectNode) { + return start(objectNode); } @Override public Node leaveObjectNode(final ObjectNode objectNode) { - newTemporary(Type.OBJECT, objectNode); - end(objectNode); - return objectNode; + ensureSymbol(Type.OBJECT, objectNode); + return end(objectNode); } + //TODO is this correct why not leave? @Override - public Node enterPropertyNode(final PropertyNode propertyNode) { + public boolean enterPropertyNode(final PropertyNode propertyNode) { // assign a pseudo symbol to property name, see NASHORN-710 + start(propertyNode); propertyNode.setSymbol(new Symbol(propertyNode.getKeyName(), 0, Type.OBJECT)); end(propertyNode); - return propertyNode; + return true; } @Override @@ -636,8 +691,10 @@ final class Attr extends NodeOperatorVisitor { if (expr.getType().isUnknown() && symbol.isParam()) { symbol.setType(Type.OBJECT); } - getCurrentFunctionNode().setReturnType(Type.widest(getCurrentFunctionNode().getReturnType(), symbol.getSymbolType())); - LOG.info("Returntype is now " + getCurrentFunctionNode().getReturnType()); + + final Type returnType = Type.widest(returnTypes.pop(), symbol.getSymbolType()); + returnTypes.push(returnType); + LOG.info("Returntype is now ", returnType); } end(returnNode); @@ -649,25 +706,29 @@ final class Attr extends NodeOperatorVisitor { public Node leaveSwitchNode(final SwitchNode switchNode) { Type type = Type.UNKNOWN; + final List<CaseNode> newCases = new ArrayList<>(); for (final CaseNode caseNode : switchNode.getCases()) { final Node test = caseNode.getTest(); + + CaseNode newCaseNode = caseNode; if (test != null) { if (test instanceof LiteralNode) { //go down to integers if we can final LiteralNode<?> lit = (LiteralNode<?>)test; if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) { if (JSType.isRepresentableAsInt(lit.getNumber())) { - caseNode.setTest(LiteralNode.newInstance(lit, lit.getInt32()).accept(this)); + newCaseNode = caseNode.setTest(LiteralNode.newInstance(lit, lit.getInt32()).accept(this)); } } } else { // the "all integer" case that CodeGenerator optimizes for currently assumes literals only type = Type.OBJECT; - break; } - type = Type.widest(type, caseNode.getTest().getType()); + type = Type.widest(type, newCaseNode.getTest().getType()); } + + newCases.add(newCaseNode); } //only optimize for all integers @@ -675,11 +736,11 @@ final class Attr extends NodeOperatorVisitor { type = Type.OBJECT; } - switchNode.setTag(newInternal(getCurrentFunctionNode().uniqueName(SWITCH_TAG_PREFIX.tag()), type)); + switchNode.setTag(newInternal(getLexicalContext().getCurrentFunction().uniqueName(SWITCH_TAG_PREFIX.symbolName()), type)); end(switchNode); - return switchNode; + return switchNode.setCases(getLexicalContext(), newCases); } @Override @@ -696,25 +757,25 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterVarNode(final VarNode varNode) { + public boolean enterVarNode(final VarNode varNode) { start(varNode); final IdentNode ident = varNode.getName(); final String name = ident.getName(); - final Symbol symbol = defineSymbol(getCurrentBlock(), name, IS_VAR, ident); + final Symbol symbol = defineSymbol(getLexicalContext().getCurrentBlock(), name, IS_VAR, ident); assert symbol != null; - LOG.info("VarNode " + varNode + " set symbol " + symbol); + LOG.info("VarNode ", varNode, " set symbol ", symbol); varNode.setSymbol(symbol); // NASHORN-467 - use before definition of vars - conservative - if (localUses.contains(ident.getName())) { + if (isLocalUse(ident.getName())) { newType(symbol, Type.OBJECT); symbol.setCanBeUndefined(); } - return varNode; + return true; } @Override @@ -734,7 +795,7 @@ final class Attr extends NodeOperatorVisitor { addLocalDef(name); final Symbol symbol = varNode.getSymbol(); - final boolean isScript = lexicalContext.getFunction(symbol.getBlock()).isProgram(); //see NASHORN-56 + final boolean isScript = getLexicalContext().getDefiningFunction(symbol).isProgram(); //see NASHORN-56 if ((init.getType().isNumeric() || init.getType().isBoolean()) && !isScript) { // Forbid integers as local vars for now as we have no way to treat them as undefined newType(symbol, init.getType()); @@ -751,14 +812,14 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveADD(final UnaryNode unaryNode) { - newTemporary(arithType(), unaryNode); + ensureSymbol(arithType(), unaryNode); end(unaryNode); return unaryNode; } @Override public Node leaveBIT_NOT(final UnaryNode unaryNode) { - newTemporary(Type.INT, unaryNode); + ensureSymbol(Type.INT, unaryNode); end(unaryNode); return unaryNode; } @@ -766,30 +827,29 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveDECINC(final UnaryNode unaryNode) { // @see assignOffset - ensureAssignmentSlots(getCurrentFunctionNode(), unaryNode.rhs()); + ensureAssignmentSlots(getLexicalContext().getCurrentFunction(), unaryNode.rhs()); final Type type = arithType(); newType(unaryNode.rhs().getSymbol(), type); - newTemporary(type, unaryNode); + ensureSymbol(type, unaryNode); end(unaryNode); return unaryNode; } @Override public Node leaveDELETE(final UnaryNode unaryNode) { - final FunctionNode currentFunctionNode = getCurrentFunctionNode(); - final boolean strictMode = currentFunctionNode.isStrictMode(); + final FunctionNode currentFunctionNode = getLexicalContext().getCurrentFunction(); + final boolean strictMode = currentFunctionNode.isStrict(); final Node rhs = unaryNode.rhs(); final Node strictFlagNode = LiteralNode.newInstance(unaryNode, strictMode).accept(this); Request request = Request.DELETE; - final RuntimeNode runtimeNode; final List<Node> args = new ArrayList<>(); if (rhs instanceof IdentNode) { // If this is a declared variable or a function parameter, delete always fails (except for globals). final String name = ((IdentNode)rhs).getName(); - final boolean failDelete = strictMode || rhs.getSymbol().isParam() || (rhs.getSymbol().isVar() && !rhs.getSymbol().isTopLevel()); + final boolean failDelete = strictMode || rhs.getSymbol().isParam() || (rhs.getSymbol().isVar() && !isProgramLevelSymbol(name)); if (failDelete && rhs.getSymbol().isThis()) { return LiteralNode.newInstance(unaryNode, true).accept(this); @@ -797,7 +857,7 @@ final class Attr extends NodeOperatorVisitor { final Node literalNode = LiteralNode.newInstance(unaryNode, name).accept(this); if (!failDelete) { - args.add(currentFunctionNode.getScopeNode()); + args.add(compilerConstant(SCOPE)); } args.add(literalNode); args.add(strictFlagNode); @@ -825,42 +885,62 @@ final class Attr extends NodeOperatorVisitor { return LiteralNode.newInstance(unaryNode, true).accept(this); } - runtimeNode = new RuntimeNode(unaryNode, request, args); - assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //clone constructor should do this + final RuntimeNode runtimeNode = new RuntimeNode(unaryNode, request, args); + assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //unary parent constructor should do this return leaveRuntimeNode(runtimeNode); } + /** + * Is the symbol denoted by the specified name in the current lexical context defined in the program level + * @param name the name of the symbol + * @return true if the symbol denoted by the specified name in the current lexical context defined in the program level. + */ + private boolean isProgramLevelSymbol(final String name) { + for(final Iterator<Block> it = getLexicalContext().getBlocks(); it.hasNext();) { + final Block next = it.next(); + if(next.getExistingSymbol(name) != null) { + return next == getLexicalContext().getFunctionBody(getLexicalContext().getOutermostFunction()); + } + } + throw new AssertionError("Couldn't find symbol " + name + " in the context"); + } + @Override public Node leaveNEW(final UnaryNode unaryNode) { - newTemporary(Type.OBJECT, unaryNode); + ensureSymbol(Type.OBJECT, unaryNode); end(unaryNode); return unaryNode; } @Override public Node leaveNOT(final UnaryNode unaryNode) { - newTemporary(Type.BOOLEAN, unaryNode); + ensureSymbol(Type.BOOLEAN, unaryNode); end(unaryNode); return unaryNode; } + private IdentNode compilerConstant(CompilerConstants cc) { + final FunctionNode functionNode = getLexicalContext().getCurrentFunction(); + final IdentNode node = new IdentNode(functionNode.getSource(), functionNode.getToken(), functionNode.getFinish(), cc.symbolName()); + node.setSymbol(functionNode.compilerConstant(cc)); + return node; + } + @Override public Node leaveTYPEOF(final UnaryNode unaryNode) { - final Node rhs = unaryNode.rhs(); - - RuntimeNode runtimeNode; + final Node rhs = unaryNode.rhs(); List<Node> args = new ArrayList<>(); if (rhs instanceof IdentNode && !rhs.getSymbol().isParam() && !rhs.getSymbol().isVar()) { - args.add(getCurrentFunctionNode().getScopeNode()); + args.add(compilerConstant(SCOPE)); args.add(LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null } else { args.add(rhs); args.add(LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this' } - runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args); + RuntimeNode runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args); assert runtimeNode.getSymbol() == unaryNode.getSymbol(); runtimeNode = (RuntimeNode)leaveRuntimeNode(runtimeNode); @@ -872,21 +952,20 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveRuntimeNode(final RuntimeNode runtimeNode) { - newTemporary(runtimeNode.getRequest().getReturnType(), runtimeNode); + ensureSymbol(runtimeNode.getRequest().getReturnType(), runtimeNode); return runtimeNode; } @Override public Node leaveSUB(final UnaryNode unaryNode) { - newTemporary(arithType(), unaryNode); + ensureSymbol(arithType(), unaryNode); end(unaryNode); return unaryNode; } @Override public Node leaveVOID(final UnaryNode unaryNode) { - final RuntimeNode runtimeNode = new RuntimeNode(unaryNode, Request.VOID); - runtimeNode.accept(this); + final RuntimeNode runtimeNode = (RuntimeNode)new RuntimeNode(unaryNode, Request.VOID).accept(this); assert runtimeNode.getSymbol().getSymbolType().isObject(); end(unaryNode); return runtimeNode; @@ -903,7 +982,7 @@ final class Attr extends NodeOperatorVisitor { ensureTypeNotUnknown(lhs); ensureTypeNotUnknown(rhs); - newTemporary(Type.widest(lhs.getType(), rhs.getType()), binaryNode); + ensureSymbol(Type.widest(lhs.getType(), rhs.getType()), binaryNode); end(binaryNode); @@ -912,7 +991,7 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveAND(final BinaryNode binaryNode) { - newTemporary(Type.OBJECT, binaryNode); + ensureSymbol(Type.OBJECT, binaryNode); end(binaryNode); return binaryNode; } @@ -921,39 +1000,39 @@ final class Attr extends NodeOperatorVisitor { * This is a helper called before an assignment. * @param binaryNode assignment node */ - private Node enterAssignmentNode(final BinaryNode binaryNode) { + private boolean enterAssignmentNode(final BinaryNode binaryNode) { start(binaryNode); final Node lhs = binaryNode.lhs(); if (lhs instanceof IdentNode) { - final Block block = getCurrentBlock(); + final Block block = getLexicalContext().getCurrentBlock(); final IdentNode ident = (IdentNode)lhs; final String name = ident.getName(); - Symbol symbol = findSymbol(getCurrentBlock(), name); + Symbol symbol = findSymbol(block, name); if (symbol == null) { symbol = defineSymbol(block, name, IS_GLOBAL, ident); binaryNode.setSymbol(symbol); - } else if (!isLocal(getCurrentFunctionNode(), symbol)) { - symbol.setIsScope(); + } else { + maybeForceScope(symbol); } addLocalDef(name); } - return binaryNode; + return true; } private boolean isLocal(FunctionNode function, Symbol symbol) { - final Block block = symbol.getBlock(); - // some temp symbols have no block, so can be assumed local - return block == null || lexicalContext.getFunction(block) == function; + final FunctionNode definingFn = getLexicalContext().getDefiningFunction(symbol); + // Temp symbols are not assigned to a block, so their defining fn is null; those can be assumed local + return definingFn == null || definingFn == function; } @Override - public Node enterASSIGN(final BinaryNode binaryNode) { + public boolean enterASSIGN(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -963,7 +1042,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_ADD(final BinaryNode binaryNode) { + public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -978,7 +1057,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_BIT_AND(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -988,7 +1067,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_BIT_OR(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -998,7 +1077,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -1008,7 +1087,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_DIV(final BinaryNode binaryNode) { + public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -1018,7 +1097,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_MOD(final BinaryNode binaryNode) { + public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -1028,7 +1107,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_MUL(final BinaryNode binaryNode) { + public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -1038,7 +1117,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_SAR(final BinaryNode binaryNode) { + public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -1048,7 +1127,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_SHL(final BinaryNode binaryNode) { + public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -1058,7 +1137,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_SHR(final BinaryNode binaryNode) { + public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -1068,7 +1147,7 @@ final class Attr extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_SUB(final BinaryNode binaryNode) { + public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { return enterAssignmentNode(binaryNode); } @@ -1094,13 +1173,13 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveCOMMARIGHT(final BinaryNode binaryNode) { - newTemporary(binaryNode.rhs().getType(), binaryNode); + ensureSymbol(binaryNode.rhs().getType(), binaryNode); return binaryNode; } @Override public Node leaveCOMMALEFT(final BinaryNode binaryNode) { - newTemporary(binaryNode.lhs().getType(), binaryNode); + ensureSymbol(binaryNode.lhs().getType(), binaryNode); return binaryNode; } @@ -1113,7 +1192,7 @@ final class Attr extends NodeOperatorVisitor { final Node lhs = binaryNode.lhs(); final Node rhs = binaryNode.rhs(); - newTemporary(Type.BOOLEAN, binaryNode); + ensureSymbol(Type.BOOLEAN, binaryNode); ensureTypeNotUnknown(lhs); ensureTypeNotUnknown(rhs); @@ -1131,7 +1210,7 @@ final class Attr extends NodeOperatorVisitor { //newType(binaryNode.lhs().getSymbol(), operandType); //newType(binaryNode.rhs().getSymbol(), operandType); - newTemporary(destType, binaryNode); + ensureSymbol(destType, binaryNode); return binaryNode; } @@ -1216,7 +1295,7 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveOR(final BinaryNode binaryNode) { - newTemporary(Type.OBJECT, binaryNode); + ensureSymbol(Type.OBJECT, binaryNode); end(binaryNode); return binaryNode; } @@ -1244,7 +1323,7 @@ final class Attr extends NodeOperatorVisitor { @Override public Node leaveForNode(final ForNode forNode) { if (forNode.isForIn()) { - forNode.setIterator(newInternal(getCurrentFunctionNode().uniqueName(ITERATOR_PREFIX.tag()), Type.OBJECT)); //NASHORN-73 + forNode.setIterator(newInternal(getLexicalContext().getCurrentFunction().uniqueName(ITERATOR_PREFIX.symbolName()), Type.OBJECT)); //NASHORN-73 /* * Iterators return objects, so we need to widen the scope of the * init variable if it, for example, has been assigned double type @@ -1267,65 +1346,50 @@ final class Attr extends NodeOperatorVisitor { ensureTypeNotUnknown(rhs); final Type type = Type.widest(lhs.getType(), rhs.getType()); - newTemporary(type, ternaryNode); + ensureSymbol(type, ternaryNode); end(ternaryNode); + assert ternaryNode.getSymbol() != null; return ternaryNode; } - private void initThis(final FunctionNode functionNode) { - final Symbol thisSymbol = defineSymbol(functionNode, THIS.tag(), IS_PARAM | IS_THIS, null); + private void initThis(final Block block) { + final Symbol thisSymbol = defineSymbol(block, THIS.symbolName(), IS_PARAM | IS_THIS, null); newType(thisSymbol, Type.OBJECT); thisSymbol.setNeedsSlot(true); - functionNode.getThisNode().setSymbol(thisSymbol); - LOG.info("Initialized scope symbol: " + thisSymbol); } - private void initScope(final FunctionNode functionNode) { - final Symbol scopeSymbol = defineSymbol(functionNode, SCOPE.tag(), IS_VAR | IS_INTERNAL, null); + private void initScope(final Block block) { + final Symbol scopeSymbol = defineSymbol(block, SCOPE.symbolName(), IS_VAR | IS_INTERNAL, null); newType(scopeSymbol, Type.typeFor(ScriptObject.class)); scopeSymbol.setNeedsSlot(true); - functionNode.getScopeNode().setSymbol(scopeSymbol); - LOG.info("Initialized scope symbol: " + scopeSymbol); } - private void initReturn(final FunctionNode functionNode) { - final Symbol returnSymbol = defineSymbol(functionNode, SCRIPT_RETURN.tag(), IS_VAR | IS_INTERNAL, null); + private void initReturn(final Block block) { + final Symbol returnSymbol = defineSymbol(block, RETURN.symbolName(), IS_VAR | IS_INTERNAL, null); newType(returnSymbol, Type.OBJECT); returnSymbol.setNeedsSlot(true); - functionNode.getResultNode().setSymbol(returnSymbol); - LOG.info("Initialized return symbol: " + returnSymbol); //return symbol is always object as it's the __return__ thing. What returnType is is another matter though } - private void initVarArg(final FunctionNode functionNode) { - if (functionNode.isVarArg()) { - final Symbol varArgsSymbol = defineSymbol(functionNode, VARARGS.tag(), IS_PARAM | IS_INTERNAL, null); - varArgsSymbol.setTypeOverride(Type.OBJECT_ARRAY); - varArgsSymbol.setNeedsSlot(true); - functionNode.getVarArgsNode().setSymbol(varArgsSymbol); - LOG.info("Initialized varargs symbol: " + varArgsSymbol); - - if (functionNode.needsArguments()) { - final String argumentsName = functionNode.getArgumentsNode().getName(); - final Symbol argumentsSymbol = defineSymbol(functionNode, argumentsName, IS_VAR | IS_INTERNAL, null); - newType(argumentsSymbol, Type.typeFor(ScriptObject.class)); - argumentsSymbol.setNeedsSlot(true); - functionNode.getArgumentsNode().setSymbol(argumentsSymbol); - addLocalDef(argumentsName); - LOG.info("Initialized vararg varArgsSymbol=" + varArgsSymbol + " argumentsSymbol=" + argumentsSymbol); - } + private void initVarArg(final Block block, final boolean needsArguments) { + final Symbol varArgsSymbol = defineSymbol(block, VARARGS.symbolName(), IS_PARAM | IS_INTERNAL, null); + varArgsSymbol.setTypeOverride(Type.OBJECT_ARRAY); + varArgsSymbol.setNeedsSlot(true); + + if (needsArguments) { + final Symbol argumentsSymbol = defineSymbol(block, ARGUMENTS.symbolName(), IS_VAR | IS_INTERNAL, null); + newType(argumentsSymbol, Type.typeFor(ScriptObject.class)); + argumentsSymbol.setNeedsSlot(true); + addLocalDef(ARGUMENTS.symbolName()); } } - private void initCallee(final FunctionNode functionNode) { - assert functionNode.getCalleeNode() != null : functionNode + " has no callee"; - final Symbol calleeSymbol = defineSymbol(functionNode, CALLEE.tag(), IS_PARAM | IS_INTERNAL, null); - newType(calleeSymbol, Type.typeFor(ScriptFunction.class)); + private void initCallee(final Block block) { + final Symbol calleeSymbol = defineSymbol(block, CALLEE.symbolName(), IS_PARAM | IS_INTERNAL, null); + newType(calleeSymbol, FunctionNode.FUNCTION_TYPE); calleeSymbol.setNeedsSlot(true); - functionNode.getCalleeNode().setSymbol(calleeSymbol); - LOG.info("Initialized callee symbol " + calleeSymbol); } /** @@ -1334,25 +1398,19 @@ final class Attr extends NodeOperatorVisitor { * * @param functionNode the function node */ - private void initParameters(final FunctionNode functionNode) { - //If a function is specialized, we don't need to tag either it return - // type or its parameters with the widest (OBJECT) type for safety. - functionNode.setReturnType(Type.UNKNOWN); - + private void initParameters(final FunctionNode functionNode, final Block body) { for (final IdentNode param : functionNode.getParameters()) { addLocalDef(param.getName()); - final Symbol paramSymbol = defineSymbol(functionNode, param.getName(), IS_PARAM, param); + final Symbol paramSymbol = defineSymbol(body, param.getName(), IS_PARAM, param); if (paramSymbol != null) { final Type callSiteParamType = functionNode.getSpecializedType(param); if (callSiteParamType != null) { - LOG.info("Param " + paramSymbol + " has a callsite type " + callSiteParamType + ". Using that."); - - System.err.println("Param " + param + " has a callsite type " + callSiteParamType + ". Using that."); + LOG.info("Param ", paramSymbol, " has a callsite type ", callSiteParamType, ". Using that."); } newType(paramSymbol, callSiteParamType == null ? Type.UNKNOWN : callSiteParamType); } - LOG.info("Initialized param " + paramSymbol); + LOG.info("Initialized param ", paramSymbol); } } @@ -1378,7 +1436,7 @@ final class Attr extends NodeOperatorVisitor { // this function, we can tell the runtime system that no matter what the // call site is, use this information. TODO if (!paramSymbol.getSymbolType().isObject()) { - LOG.finest("Parameter " + ident + " could profit from specialization to " + paramSymbol.getSymbolType()); + LOG.finest("Parameter ", ident, " could profit from specialization to ", paramSymbol.getSymbolType()); } newType(paramSymbol, Type.widest(type, paramSymbol.getSymbolType())); @@ -1392,19 +1450,18 @@ final class Attr extends NodeOperatorVisitor { /** * Move any properties from a global map into the scope of this method - * @param functionNode the function node for which to init scope vars + * @param block the function node body for which to init scope vars */ - private void initFromPropertyMap(final FunctionNode functionNode) { + private void initFromPropertyMap(final Block block) { // For a script, add scope symbols as defined in the property map - assert functionNode.isProgram(); final PropertyMap map = Context.getGlobalMap(); for (final Property property : map.getProperties()) { final String key = property.getKey(); - final Symbol symbol = defineSymbol(functionNode, key, IS_GLOBAL, null); + final Symbol symbol = defineSymbol(block, key, IS_GLOBAL, null); newType(symbol, Type.OBJECT); - LOG.info("Added global symbol from property map " + symbol); + LOG.info("Added global symbol from property map ", symbol); } } @@ -1412,7 +1469,7 @@ final class Attr extends NodeOperatorVisitor { final Symbol symbol = node.getSymbol(); - LOG.info("Ensure type not unknown for: " + symbol); + LOG.info("Ensure type not unknown for: ", symbol); /* * Note that not just unknowns, but params need to be blown @@ -1452,7 +1509,7 @@ final class Attr extends NodeOperatorVisitor { } private Symbol exceptionSymbol() { - return newInternal(getCurrentFunctionNode().uniqueName(EXCEPTION_PREFIX.tag()), Type.typeFor(ECMAException.class)); + return newInternal(getLexicalContext().getCurrentFunction().uniqueName(EXCEPTION_PREFIX.symbolName()), Type.typeFor(ECMAException.class)); } /** @@ -1512,15 +1569,15 @@ final class Attr extends NodeOperatorVisitor { } Type from = node.getType(); if (!Type.areEquivalent(from, to) && Type.widest(from, to) == to) { - LOG.fine("Had to post pass widen '" + node + "' " + Debug.id(node) + " from " + node.getType() + " to " + to); + LOG.fine("Had to post pass widen '", node, "' " + Debug.id(node), " from ", node.getType(), " to ", to); newType(node.getSymbol(), to); changed.add(node); } } @Override - public Node enterFunctionNode(final FunctionNode node) { - return node.isLazy() ? null : node; + public boolean enterFunctionNode(final FunctionNode node) { + return !node.isLazy(); } /** @@ -1574,7 +1631,7 @@ final class Attr extends NodeOperatorVisitor { } else { type = Type.OBJECT; //force lhs to be an object if not numeric assignment, e.g. strings too. } - newTemporary(type, binaryNode); + ensureSymbol(type, binaryNode); newType(lhs.getSymbol(), type); end(binaryNode); return binaryNode; @@ -1589,32 +1646,25 @@ final class Attr extends NodeOperatorVisitor { final Node lhs = binaryNode.lhs(); newType(lhs.getSymbol(), destType); //may not narrow if dest is already wider than destType - newTemporary(destType, binaryNode); //for OP= nodes, the node can carry a narrower types than its lhs rhs. This is perfectly fine + ensureSymbol(destType, binaryNode); //for OP= nodes, the node can carry a narrower types than its lhs rhs. This is perfectly fine - ensureAssignmentSlots(getCurrentFunctionNode(), binaryNode); + ensureAssignmentSlots(getLexicalContext().getCurrentFunction(), binaryNode); end(binaryNode); return binaryNode; } - private static boolean isFunctionExpressionSelfReference(final Symbol symbol) { - if (symbol.isVar() && symbol.getNode() == symbol.getBlock() && symbol.getNode() instanceof FunctionNode) { - return ((FunctionNode)symbol.getNode()).getIdent().getName().equals(symbol.getName()); - } - return false; - } - - private static Symbol newTemporary(final FunctionNode functionNode, final Type type, final Node node) { - LOG.info("New TEMPORARY added to " + functionNode.getName() + " type=" + type); - return functionNode.newTemporary(type, node); + private Symbol ensureSymbol(final FunctionNode functionNode, final Type type, final Node node) { + LOG.info("New TEMPORARY added to ", functionNode.getName(), " type=", type); + return functionNode.ensureSymbol(getLexicalContext().getCurrentBlock(), type, node); } - private Symbol newTemporary(final Type type, final Node node) { - return newTemporary(getCurrentFunctionNode(), type, node); + private Symbol ensureSymbol(final Type type, final Node node) { + return ensureSymbol(getLexicalContext().getCurrentFunction(), type, node); } private Symbol newInternal(final String name, final Type type) { - final Symbol iter = defineSymbol(getCurrentFunctionNode(), name, IS_VAR | IS_INTERNAL, null); + final Symbol iter = defineSymbol(getLexicalContext().getCurrentBlock(), name, IS_VAR | IS_INTERNAL, null); iter.setType(type); // NASHORN-73 return iter; } @@ -1624,40 +1674,51 @@ final class Attr extends NodeOperatorVisitor { symbol.setType(type); if (symbol.getSymbolType() != oldType) { - LOG.info("New TYPE " + type + " for " + symbol + " (was " + oldType + ")"); + LOG.info("New TYPE ", type, " for ", symbol," (was ", oldType, ")"); } if (symbol.isParam()) { symbol.setType(type); - LOG.info("Param type change " + symbol); + LOG.info("Param type change ", symbol); } } - private void clearLocalDefs() { - localDefs = new HashSet<>(); + private void pushLocalsFunction() { + localDefs.push(new HashSet<String>()); + localUses.push(new HashSet<String>()); + } + + private void pushLocalsBlock() { + localDefs.push(localDefs.isEmpty() ? new HashSet<String>() : new HashSet<>(localDefs.peek())); + localUses.push(localUses.isEmpty() ? new HashSet<String>() : new HashSet<>(localUses.peek())); + } + + private void popLocals() { + localDefs.pop(); + localUses.pop(); } private boolean isLocalDef(final String name) { - return localDefs.contains(name); + return localDefs.peek().contains(name); } private void addLocalDef(final String name) { - LOG.info("Adding local def of symbol: '" + name + "'"); - localDefs.add(name); + LOG.info("Adding local def of symbol: '", name, "'"); + localDefs.peek().add(name); } private void removeLocalDef(final String name) { - LOG.info("Removing local def of symbol: '" + name + "'"); - localDefs.remove(name); + LOG.info("Removing local def of symbol: '", name, "'"); + localDefs.peek().remove(name); } - private void clearLocalUses() { - localUses = new HashSet<>(); + private boolean isLocalUse(final String name) { + return localUses.peek().contains(name); } private void addLocalUse(final String name) { - LOG.info("Adding local use of symbol: '" + name + "'"); - localUses.add(name); + LOG.info("Adding local use of symbol: '", name, "'"); + localUses.peek().add(name); } /** @@ -1665,30 +1726,28 @@ final class Attr extends NodeOperatorVisitor { * This is done when the function contains unevaluated black boxes such as * lazy sub-function nodes that have not been compiled. * - * @param functionNode function node in whose scope symbols should conservatively be made objects + * @param body body for the function node we are leaving */ - private static void objectifySymbols(final FunctionNode functionNode) { - functionNode.accept(new NodeVisitor() { + private static void objectifySymbols(final Block body) { + body.accept(new NodeVisitor() { private void toObject(final Block block) { for (final Iterator<Symbol> iter = block.symbolIterator(); iter.hasNext();) { final Symbol symbol = iter.next(); - newType(symbol, Type.OBJECT); + if (!symbol.isTemp()) { + newType(symbol, Type.OBJECT); + } } } @Override - public Node enterBlock(final Block block) { + public boolean enterBlock(final Block block) { toObject(block); - return block; + return true; } @Override - public Node enterFunctionNode(final FunctionNode node) { - toObject(node); - if (node.isLazy()) { - return null; - } - return node; + public boolean enterFunctionNode(final FunctionNode node) { + return false; } }); } @@ -1702,11 +1761,11 @@ final class Attr extends NodeOperatorVisitor { return cn.substring(lastDot + 1); } - private Node start(final Node node) { + private boolean start(final Node node) { return start(node, true); } - private Node start(final Node node, final boolean printNode) { + private boolean start(final Node node, final boolean printNode) { if (DEBUG) { final StringBuilder sb = new StringBuilder(); @@ -1715,13 +1774,13 @@ final class Attr extends NodeOperatorVisitor { append("] "). append(printNode ? node.toString() : ""). append(" in '"). - append(getCurrentFunctionNode().getName()). + append(getLexicalContext().getCurrentFunction().getName()). append("'"); - LOG.info(sb.toString()); + LOG.info(sb); LOG.indent(); } - return node; + return true; } private Node end(final Node node) { @@ -1737,7 +1796,7 @@ final class Attr extends NodeOperatorVisitor { append("] "). append(printNode ? node.toString() : ""). append(" in '"). - append(getCurrentFunctionNode().getName()); + append(getLexicalContext().getCurrentFunction().getName()); if (node.getSymbol() == null) { sb.append(" <NO SYMBOL>"); @@ -1746,7 +1805,7 @@ final class Attr extends NodeOperatorVisitor { } LOG.unindent(); - LOG.info(sb.toString()); + LOG.info(sb); } return node; diff --git a/src/jdk/nashorn/internal/codegen/ClassEmitter.java b/src/jdk/nashorn/internal/codegen/ClassEmitter.java index 7ca7f994..2d379183 100644 --- a/src/jdk/nashorn/internal/codegen/ClassEmitter.java +++ b/src/jdk/nashorn/internal/codegen/ClassEmitter.java @@ -58,12 +58,14 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; + import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.util.TraceClassVisitor; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.SplitNode; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptObject; @@ -219,14 +221,14 @@ public class ClassEmitter implements Emitter { private void defineCommonStatics(final boolean strictMode) { // source - used to store the source data (text) for this script. Shared across // compile units. Set externally by the compiler. - field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), SOURCE.tag(), Source.class); + field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), SOURCE.symbolName(), Source.class); // constants - used to the constants array for this script. Shared across // compile units. Set externally by the compiler. - field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), CONSTANTS.tag(), Object[].class); + field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), CONSTANTS.symbolName(), Object[].class); // strictMode - was this script compiled in strict mode. Set externally by the compiler. - field(EnumSet.of(Flag.PUBLIC, Flag.STATIC, Flag.FINAL), STRICT_MODE.tag(), boolean.class, strictMode); + field(EnumSet.of(Flag.PUBLIC, Flag.STATIC, Flag.FINAL), STRICT_MODE.symbolName(), boolean.class, strictMode); } /** @@ -238,9 +240,9 @@ public class ClassEmitter implements Emitter { if (constantMethodNeeded.contains(String.class)) { // $getString - get the ith entry from the constants table and cast to String. - final MethodEmitter getStringMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), GET_STRING.tag(), String.class, int.class); + final MethodEmitter getStringMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), GET_STRING.symbolName(), String.class, int.class); getStringMethod.begin(); - getStringMethod.getStatic(unitClassName, CONSTANTS.tag(), CONSTANTS.descriptor()) + getStringMethod.getStatic(unitClassName, CONSTANTS.symbolName(), CONSTANTS.descriptor()) .load(Type.INT, 0) .arrayload() .checkcast(String.class) @@ -250,7 +252,7 @@ public class ClassEmitter implements Emitter { if (constantMethodNeeded.contains(PropertyMap.class)) { // $getMap - get the ith entry from the constants table and cast to PropertyMap. - final MethodEmitter getMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), GET_MAP.tag(), PropertyMap.class, int.class); + final MethodEmitter getMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), GET_MAP.symbolName(), PropertyMap.class, int.class); getMapMethod.begin(); getMapMethod.loadConstants() .load(Type.INT, 0) @@ -260,7 +262,7 @@ public class ClassEmitter implements Emitter { getMapMethod.end(); // $setMap - overwrite an existing map. - final MethodEmitter setMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), SET_MAP.tag(), void.class, int.class, PropertyMap.class); + final MethodEmitter setMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), SET_MAP.symbolName(), void.class, int.class, PropertyMap.class); setMapMethod.begin(); setMapMethod.loadConstants() .load(Type.INT, 0) @@ -289,7 +291,7 @@ public class ClassEmitter implements Emitter { final MethodEmitter getArrayMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), methodName, cls, int.class); getArrayMethod.begin(); - getArrayMethod.getStatic(unitClassName, CONSTANTS.tag(), CONSTANTS.descriptor()) + getArrayMethod.getStatic(unitClassName, CONSTANTS.symbolName(), CONSTANTS.descriptor()) .load(Type.INT, 0) .arrayload() .checkcast(cls) @@ -307,7 +309,7 @@ public class ClassEmitter implements Emitter { */ static String getArrayMethodName(final Class<?> cls) { assert cls.isArray(); - return GET_ARRAY_PREFIX.tag() + cls.getComponentType().getSimpleName() + GET_ARRAY_SUFFIX.tag(); + return GET_ARRAY_PREFIX.symbolName() + cls.getComponentType().getSimpleName() + GET_ARRAY_SUFFIX.symbolName(); } /** @@ -409,6 +411,10 @@ public class ClassEmitter implements Emitter { methodsStarted.remove(method); } + SplitMethodEmitter method(final SplitNode splitNode, final String methodName, final Class<?> rtype, final Class<?>... ptypes) { + return new SplitMethodEmitter(this, methodVisitor(EnumSet.of(Flag.PUBLIC, Flag.STATIC), methodName, rtype, ptypes), splitNode); + } + /** * Add a new method to the class - defaults to public method * @@ -433,7 +439,7 @@ public class ClassEmitter implements Emitter { * @return method emitter to use for weaving this method */ MethodEmitter method(final EnumSet<Flag> methodFlags, final String methodName, final Class<?> rtype, final Class<?>... ptypes) { - return new MethodEmitter(this, cw.visitMethod(Flag.getValue(methodFlags), methodName, methodDescriptor(rtype, ptypes), null, null)); + return new MethodEmitter(this, methodVisitor(methodFlags, methodName, rtype, ptypes)); } /** @@ -484,7 +490,7 @@ public class ClassEmitter implements Emitter { * @return method emitter to use for weaving <clinit> */ MethodEmitter clinit() { - return method(EnumSet.of(Flag.STATIC), CLINIT.tag(), void.class); + return method(EnumSet.of(Flag.STATIC), CLINIT.symbolName(), void.class); } /** @@ -493,7 +499,7 @@ public class ClassEmitter implements Emitter { * @return method emitter to use for weaving <init>()V */ MethodEmitter init() { - return method(INIT.tag(), void.class); + return method(INIT.symbolName(), void.class); } /** @@ -503,7 +509,7 @@ public class ClassEmitter implements Emitter { * @return method emitter to use for weaving <init>()V */ MethodEmitter init(final Class<?>... ptypes) { - return method(INIT.tag(), void.class, ptypes); + return method(INIT.symbolName(), void.class, ptypes); } /** @@ -515,7 +521,7 @@ public class ClassEmitter implements Emitter { * @return method emitter to use for weaving <init>(...)V */ MethodEmitter init(final EnumSet<Flag> flags, final Class<?>... ptypes) { - return method(flags, INIT.tag(), void.class, ptypes); + return method(flags, INIT.symbolName(), void.class, ptypes); } /** @@ -628,4 +634,9 @@ public class ClassEmitter implements Emitter { return v; } } + + private MethodVisitor methodVisitor(EnumSet<Flag> flags, final String methodName, final Class<?> rtype, final Class<?>... ptypes) { + return cw.visitMethod(Flag.getValue(flags), methodName, methodDescriptor(rtype, ptypes), null, null); + } + } diff --git a/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/src/jdk/nashorn/internal/codegen/CodeGenerator.java index 1d09e9c9..5d4b8f98 100644 --- a/src/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -27,14 +27,18 @@ package jdk.nashorn.internal.codegen; import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.PRIVATE; import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.STATIC; +import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP; import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING; -import static jdk.nashorn.internal.codegen.CompilerConstants.LEAF; import static jdk.nashorn.internal.codegen.CompilerConstants.QUICK_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.REGEX_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_ARRAY_ARG; import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; +import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; @@ -48,8 +52,10 @@ import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALL import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT; import java.io.PrintWriter; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Deque; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; @@ -67,11 +73,11 @@ import jdk.nashorn.internal.ir.BaseNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.BreakNode; +import jdk.nashorn.internal.ir.BreakableNode; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; -import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.EmptyNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; @@ -81,10 +87,12 @@ import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.LexicalContext; +import jdk.nashorn.internal.ir.LexicalContextNode; import jdk.nashorn.internal.ir.LineNumberNode; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit; +import jdk.nashorn.internal.ir.LoopNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.ObjectNode; import jdk.nashorn.internal.ir.PropertyNode; @@ -107,8 +115,10 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.parser.Lexer.RegexToken; import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.Debug; import jdk.nashorn.internal.runtime.DebugLogger; import jdk.nashorn.internal.runtime.ECMAException; +import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.Property; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; @@ -157,14 +167,29 @@ final class CodeGenerator extends NodeOperatorVisitor { /** How many regexp fields have been emitted */ private int regexFieldCount; - /** Used for temporary signaling between enterCallNode and enterFunctionNode to handle the special case of calling - * a just-defined anonymous function expression. */ - private boolean functionNodeIsCallee; - /** Map of shared scope call sites */ private final Map<SharedScopeCall, SharedScopeCall> scopeCalls = new HashMap<>(); - private final LexicalContext lexicalContext = new LexicalContext(); + /** Compile unit stack - every time we start a sub method (e.g. a split) we push one */ + private final Deque<CompileUnit> compileUnits = new ArrayDeque<>(); + + /** Method emitter stack - every time we start a sub method (e.g. a split) we push one */ + private final Deque<MethodEmitter> methodEmitters = new ArrayDeque<>(); + + /** The discard stack - whenever we enter a discard node we keep track of its return value status - + * i.e. should we keep it or throw it away */ + private final Deque<Node> discard = new ArrayDeque<>(); + + // A stack tracking the next free local variable slot in the blocks. There's one entry for every block + // currently on the lexical context stack. + private int[] nextFreeSlots = new int[16]; + private int nextFreeSlotsSize = 0; + + /** Current method emitter */ + private MethodEmitter method; + + /** Current compile unit */ + private CompileUnit unit; /** When should we stop caching regexp expressions in fields to limit bytecode size? */ private static final int MAX_REGEX_FIELDS = 2 * 1024; @@ -177,6 +202,7 @@ final class CodeGenerator extends NodeOperatorVisitor { * @param compiler */ CodeGenerator(final Compiler compiler) { + super(new DynamicScopeTrackingLexicalContext()); this.compiler = compiler; this.callSiteFlags = compiler.getEnv()._callsite_flags; } @@ -188,7 +214,37 @@ final class CodeGenerator extends NodeOperatorVisitor { * @return the correct flags for a call site in the current function */ int getCallSiteFlags() { - return getCurrentFunctionNode().isStrictMode() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags; + return getLexicalContext().getCurrentFunction().isStrict() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags; + } + + private void pushMethodEmitter(final MethodEmitter newMethod) { + methodEmitters.push(newMethod); + this.method = newMethod; + } + + private void popMethodEmitter(final MethodEmitter oldMethod) { + assert methodEmitters.peek() == oldMethod; + methodEmitters.pop(); + if (!methodEmitters.isEmpty()) { + this.method = methodEmitters.peek(); + } else { + this.method = null; + } + } + + private void push(final CompileUnit newUnit) { + compileUnits.push(newUnit); + this.unit = newUnit; + } + + private void pop(final CompileUnit oldUnit) { + assert compileUnits.peek() == oldUnit; + compileUnits.pop(); + if (!compileUnits.isEmpty()) { + this.unit = compileUnits.peek(); + } else { + this.unit = null; + } } /** @@ -217,7 +273,7 @@ final class CodeGenerator extends NodeOperatorVisitor { assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; final int flags = CALLSITE_SCOPE | getCallSiteFlags(); - method.loadScope(); + method.loadCompilerConstant(SCOPE); if (isFastScope(symbol)) { // Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope. @@ -231,27 +287,103 @@ final class CodeGenerator extends NodeOperatorVisitor { } /** + * A lexical context that also tracks if we have any dynamic scopes in the context. Such scopes can have new + * variables introduced into them at run time - a with block or a function directly containing an eval call. + */ + private static class DynamicScopeTrackingLexicalContext extends LexicalContext { + int dynamicScopeCount = 0; + + @Override + public <T extends LexicalContextNode> T push(T node) { + if(isDynamicScopeBoundary(node)) { + ++dynamicScopeCount; + } + return super.push(node); + } + + @Override + public <T extends LexicalContextNode> T pop(T node) { + final T popped = super.pop(node); + if(isDynamicScopeBoundary(popped)) { + --dynamicScopeCount; + } + return popped; + } + + private boolean isDynamicScopeBoundary(LexicalContextNode node) { + if(node instanceof Block) { + // Block's immediate parent is a with node. Note we aren't testing for a WithNode, as that'd capture + // processing of WithNode.expression too, but it should be unaffected. + return !isEmpty() && peek() instanceof WithNode; + } else if(node instanceof FunctionNode) { + // Function has a direct eval in it (so a top-level "var ..." in the eval code can introduce a new + // variable into the function's scope), and it isn't strict (as evals in strict functions get an + // isolated scope). + return isFunctionDynamicScope((FunctionNode)node); + } + return false; + } + } + + boolean inDynamicScope() { + return ((DynamicScopeTrackingLexicalContext)getLexicalContext()).dynamicScopeCount > 0; + } + + static boolean isFunctionDynamicScope(FunctionNode fn) { + return fn.hasEval() && !fn.isStrict(); + } + + /** * Check if this symbol can be accessed directly with a putfield or getfield or dynamic load * * @param function function to check for fast scope * @return true if fast scope */ private boolean isFastScope(final Symbol symbol) { - if (!symbol.isScope() || !symbol.getBlock().needsScope()) { + if(!symbol.isScope()) { return false; } - // Allow fast scope access if no function contains with or eval - for(final Iterator<FunctionNode> it = lexicalContext.getFunctions(getCurrentFunctionNode()); it.hasNext();) { - final FunctionNode func = it.next(); - if (func.hasWith() || func.hasEval()) { - return false; + final LexicalContext lc = getLexicalContext(); + if(!inDynamicScope()) { + // If there's no with or eval in context, and the symbol is marked as scoped, it is fast scoped. Such a + // symbol must either be global, or its defining block must need scope. + assert symbol.isGlobal() || lc.getDefiningBlock(symbol).needsScope() : symbol.getName(); + return true; + } + if(symbol.isGlobal()) { + // Shortcut: if there's a with or eval in context, globals can't be fast scoped + return false; + } + // Otherwise, check if there's a dynamic scope between use of the symbol and its definition + final String name = symbol.getName(); + boolean previousWasBlock = false; + for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { + final LexicalContextNode node = it.next(); + if(node instanceof Block) { + // If this block defines the symbol, then we can fast scope the symbol. + final Block block = (Block)node; + if(block.getExistingSymbol(name) == symbol) { + assert block.needsScope(); + return true; + } + previousWasBlock = true; + } else { + if((node instanceof WithNode && previousWasBlock) || (node instanceof FunctionNode && isFunctionDynamicScope((FunctionNode)node))) { + // If we hit a scope that can have symbols introduced into it at run time before finding the defining + // block, the symbol can't be fast scoped. A WithNode only counts if we've immediately seen a block + // before - its block. Otherwise, we are currently processing the WithNode's expression, and that's + // obviously not subjected to introducing new symbols. + return false; + } + previousWasBlock = false; } } - return true; + // Should've found the symbol defined in a block + throw new AssertionError(); } private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) { - method.load(isFastScope(symbol) ? getScopeProtoDepth(getCurrentBlock(), symbol) : -1); + method.load(isFastScope(symbol) ? getScopeProtoDepth(getLexicalContext().getCurrentBlock(), symbol) : -1); final SharedScopeCall scopeCall = getScopeGet(valueType, symbol, flags | CALLSITE_FAST_SCOPE); scopeCall.generateInvoke(method); return method; @@ -271,10 +403,10 @@ final class CodeGenerator extends NodeOperatorVisitor { private int getScopeProtoDepth(final Block startingBlock, final Symbol symbol) { int depth = 0; - final Block definingBlock = symbol.getBlock(); - for(final Iterator<Block> blocks = lexicalContext.getBlocks(startingBlock); blocks.hasNext();) { + final String name = symbol.getName(); + for(final Iterator<Block> blocks = getLexicalContext().getBlocks(startingBlock); blocks.hasNext();) { final Block currentBlock = blocks.next(); - if (currentBlock == definingBlock) { + if (currentBlock.getExistingSymbol(name) == symbol) { return depth; } if (currentBlock.needsScope()) { @@ -285,9 +417,9 @@ final class CodeGenerator extends NodeOperatorVisitor { } private void loadFastScopeProto(final Symbol symbol, final boolean swap) { - final int depth = getScopeProtoDepth(getCurrentBlock(), symbol); + final int depth = getScopeProtoDepth(getLexicalContext().getCurrentBlock(), symbol); assert depth != -1; - if(depth > 0) { + if (depth > 0) { if (swap) { method.swap(); } @@ -328,46 +460,46 @@ final class CodeGenerator extends NodeOperatorVisitor { */ final CodeGenerator codegen = this; - node.accept(new NodeVisitor(getCurrentCompileUnit(), method) { + node.accept(new NodeVisitor() { @Override - public Node enterIdentNode(final IdentNode identNode) { + public boolean enterIdentNode(final IdentNode identNode) { loadIdent(identNode); - return null; + return false; } @Override - public Node enterAccessNode(final AccessNode accessNode) { + public boolean enterAccessNode(final AccessNode accessNode) { if (!baseAlreadyOnStack) { load(accessNode.getBase()).convert(Type.OBJECT); } assert method.peekType().isObject(); method.dynamicGet(node.getType(), accessNode.getProperty().getName(), getCallSiteFlags(), accessNode.isFunction()); - return null; + return false; } @Override - public Node enterIndexNode(final IndexNode indexNode) { + public boolean enterIndexNode(final IndexNode indexNode) { if (!baseAlreadyOnStack) { load(indexNode.getBase()).convert(Type.OBJECT); load(indexNode.getIndex()); } method.dynamicGetIndex(node.getType(), getCallSiteFlags(), indexNode.isFunction()); - return null; + return false; } @Override - public Node enterFunctionNode(FunctionNode functionNode) { + public boolean enterFunctionNode(FunctionNode functionNode) { // function nodes will always leave a constructed function object on stack, no need to load the symbol // separately as in enterDefault() functionNode.accept(codegen); - return null; + return false; } @Override - public Node enterDefault(final Node otherNode) { + public boolean enterDefault(final Node otherNode) { otherNode.accept(codegen); // generate code for whatever we are looking at. method.load(symbol); // load the final symbol to the stack (or nop if no slot, then result is already there) - return null; + return false; } }); @@ -375,14 +507,9 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterAccessNode(final AccessNode accessNode) { - if (accessNode.testResolved()) { - return null; - } - + public boolean enterAccessNode(final AccessNode accessNode) { load(accessNode); - - return null; + return false; } /** @@ -407,7 +534,7 @@ final class CodeGenerator extends NodeOperatorVisitor { final boolean isInternal = symbol.isParam() || symbol.isInternal() || symbol.isThis() || !symbol.canBeUndefined(); if (symbol.hasSlot() && !isInternal) { - assert symbol.getSymbolType().isNumber() || symbol.getSymbolType().isObject() : "no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + getCurrentFunctionNode(); + assert symbol.getSymbolType().isNumber() || symbol.getSymbolType().isObject() : "no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + getLexicalContext().getCurrentFunction(); if (symbol.getSymbolType().isNumber()) { numbers.add(symbol); } else if (symbol.getSymbolType().isObject()) { @@ -441,22 +568,20 @@ final class CodeGenerator extends NodeOperatorVisitor { * @param block block containing symbols. */ private void symbolInfo(final Block block) { - for (final Symbol symbol : block.getFrame().getSymbols()) { - method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel()); + for (final Iterator<Symbol> iter = block.symbolIterator(); iter.hasNext(); ) { + final Symbol symbol = iter.next(); + if (symbol.hasSlot()) { + method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel()); + } } } @Override - public Node enterBlock(final Block block) { - if (block.testResolved()) { - return null; - } - lexicalContext.push(block); - + public boolean enterBlock(final Block block) { method.label(block.getEntryLabel()); initLocals(block); - return block; + return true; } @Override @@ -464,10 +589,10 @@ final class CodeGenerator extends NodeOperatorVisitor { method.label(block.getBreakLabel()); symbolInfo(block); - if (block.needsScope()) { + if (block.needsScope() && !block.isTerminal()) { popBlockScope(block); } - lexicalContext.pop(block); + --nextFreeSlotsSize; return block; } @@ -477,34 +602,30 @@ final class CodeGenerator extends NodeOperatorVisitor { final Label skipLabel = new Label("skip_catch"); /* pop scope a la try-finally */ - method.loadScope(); + method.loadCompilerConstant(SCOPE); method.invoke(ScriptObject.GET_PROTO); - method.storeScope(); + method.storeCompilerConstant(SCOPE); method._goto(skipLabel); method.label(exitLabel); method._catch(recoveryLabel); - method.loadScope(); + method.loadCompilerConstant(SCOPE); method.invoke(ScriptObject.GET_PROTO); - method.storeScope(); + method.storeCompilerConstant(SCOPE); method.athrow(); method.label(skipLabel); method._try(block.getEntryLabel(), exitLabel, recoveryLabel, Throwable.class); } @Override - public Node enterBreakNode(final BreakNode breakNode) { - if (breakNode.testResolved()) { - return null; - } - - for (int i = 0; i < breakNode.getScopeNestingLevel(); i++) { + public boolean enterBreakNode(final BreakNode breakNode) { + final BreakableNode breakFrom = getLexicalContext().getBreakable(breakNode.getLabel()); + for (int i = 0; i < getLexicalContext().getScopeNestingLevelTo(breakFrom); i++) { closeWith(); } + method.splitAwareGoto(getLexicalContext(), breakFrom.getBreakLabel()); - method.splitAwareGoto(breakNode.getTargetLabel()); - - return null; + return false; } private int loadArgs(final List<Node> args) { @@ -541,21 +662,17 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterCallNode(final CallNode callNode) { - if (callNode.testResolved()) { - return null; - } - + public boolean enterCallNode(final CallNode callNode) { final List<Node> args = callNode.getArgs(); final Node function = callNode.getFunction(); - final Block currentBlock = getCurrentBlock(); + final Block currentBlock = getLexicalContext().getCurrentBlock(); - function.accept(new NodeVisitor(getCurrentCompileUnit(), method) { + function.accept(new NodeVisitor() { private void sharedScopeCall(final IdentNode identNode, final int flags) { final Symbol symbol = identNode.getSymbol(); int scopeCallFlags = flags; - method.loadScope(); + method.loadCompilerConstant(SCOPE); if (isFastScope(symbol)) { method.load(getScopeProtoDepth(currentBlock, symbol)); scopeCallFlags |= CALLSITE_FAST_SCOPE; @@ -591,7 +708,7 @@ final class CodeGenerator extends NodeOperatorVisitor { // We don't need ScriptFunction object for 'eval' method.pop(); - method.loadScope(); // Load up self (scope). + method.loadCompilerConstant(SCOPE); // Load up self (scope). final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); // load evaluated code @@ -618,7 +735,7 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterIdentNode(final IdentNode node) { + public boolean enterIdentNode(final IdentNode node) { final Symbol symbol = node.getSymbol(); if (symbol.isScope()) { @@ -626,27 +743,27 @@ final class CodeGenerator extends NodeOperatorVisitor { final int useCount = symbol.getUseCount(); // Threshold for generating shared scope callsite is lower for fast scope symbols because we know - // we can dial in the correct scope. However, we als need to enable it for non-fast scopes to + // we can dial in the correct scope. However, we also need to enable it for non-fast scopes to // support huge scripts like mandreel.js. if (callNode.isEval()) { evalCall(node, flags); } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD || (!isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD) - || callNode.inWithBlock()) { + || CodeGenerator.this.inDynamicScope()) { scopeCall(node, flags); } else { sharedScopeCall(node, flags); } - assert method.peekType().equals(callNode.getType()); + assert method.peekType().equals(callNode.getType()) : method.peekType() + "!=" + callNode.getType(); } else { enterDefault(node); } - return null; + return false; } @Override - public Node enterAccessNode(final AccessNode node) { + public boolean enterAccessNode(final AccessNode node) { load(node.getBase()); method.convert(Type.OBJECT); method.dup(); @@ -655,35 +772,34 @@ final class CodeGenerator extends NodeOperatorVisitor { method.dynamicCall(callNode.getType(), 2 + loadArgs(args), getCallSiteFlags()); assert method.peekType().equals(callNode.getType()); - return null; + return false; } @Override - public Node enterFunctionNode(final FunctionNode callee) { + public boolean enterFunctionNode(final FunctionNode origCallee) { + // NOTE: visiting the callee will leave a constructed ScriptFunction object on the stack if + // callee.needsCallee() == true + final FunctionNode callee = (FunctionNode)origCallee.accept(CodeGenerator.this); + final boolean isVarArg = callee.isVarArg(); final int argCount = isVarArg ? -1 : callee.getParameters().size(); final String signature = new FunctionSignature(true, callee.needsCallee(), callee.getReturnType(), isVarArg ? null : callee.getParameters()).toString(); - if (callee.needsCallee()) { - newFunctionObject(callee); - } - - if (callee.isStrictMode()) { // self is undefined + if (callee.isStrict()) { // self is undefined method.loadUndefined(Type.OBJECT); } else { // get global from scope (which is the self) globalInstance(); } loadArgs(args, signature, isVarArg, argCount); + assert callee.getCompileUnit() != null : "no compile unit for " + callee.getName() + " " + Debug.id(callee) + " " + callNode; method.invokestatic(callee.getCompileUnit().getUnitClassName(), callee.getName(), signature); assert method.peekType().equals(callee.getReturnType()) : method.peekType() + " != " + callee.getReturnType(); - functionNodeIsCallee = true; - callee.accept(CodeGenerator.this); - return null; + return false; } @Override - public Node enterIndexNode(final IndexNode node) { + public boolean enterIndexNode(final IndexNode node) { load(node.getBase()); method.convert(Type.OBJECT); method.dup(); @@ -697,11 +813,11 @@ final class CodeGenerator extends NodeOperatorVisitor { method.dynamicCall(callNode.getType(), 2 + loadArgs(args), getCallSiteFlags()); assert method.peekType().equals(callNode.getType()); - return null; + return false; } @Override - protected Node enterDefault(final Node node) { + protected boolean enterDefault(final Node node) { // Load up function. load(function); method.convert(Type.OBJECT); //TODO, e.g. booleans can be used as functions @@ -709,58 +825,41 @@ final class CodeGenerator extends NodeOperatorVisitor { method.dynamicCall(callNode.getType(), 2 + loadArgs(args), getCallSiteFlags() | CALLSITE_SCOPE); assert method.peekType().equals(callNode.getType()); - return null; + return false; } }); method.store(callNode.getSymbol()); - return null; + return false; } @Override - public Node enterContinueNode(final ContinueNode continueNode) { - if (continueNode.testResolved()) { - return null; - } - - for (int i = 0; i < continueNode.getScopeNestingLevel(); i++) { + public boolean enterContinueNode(final ContinueNode continueNode) { + final LoopNode continueTo = getLexicalContext().getContinueTo(continueNode.getLabel()); + for (int i = 0; i < getLexicalContext().getScopeNestingLevelTo(continueTo); i++) { closeWith(); } + method.splitAwareGoto(getLexicalContext(), continueTo.getContinueLabel()); - method.splitAwareGoto(continueNode.getTargetLabel()); - - return null; - } - - @Override - public Node enterDoWhileNode(final DoWhileNode doWhileNode) { - return enterWhileNode(doWhileNode); + return false; } @Override - public Node enterEmptyNode(final EmptyNode emptyNode) { - return null; + public boolean enterEmptyNode(final EmptyNode emptyNode) { + return false; } @Override - public Node enterExecuteNode(final ExecuteNode executeNode) { - if (executeNode.testResolved()) { - return null; - } - + public boolean enterExecuteNode(final ExecuteNode executeNode) { final Node expression = executeNode.getExpression(); expression.accept(this); - return null; + return false; } @Override - public Node enterForNode(final ForNode forNode) { - if (forNode.testResolved()) { - return null; - } - + public boolean enterForNode(final ForNode forNode) { final Node test = forNode.getTest(); final Block body = forNode.getBody(); final Node modify = forNode.getModify(); @@ -790,6 +889,10 @@ final class CodeGenerator extends NodeOperatorVisitor { new Store<Node>(init) { @Override + protected void storeNonDiscard() { + return; + } + @Override protected void evaluate() { method.load(iter); method.invoke(interfaceCallNoLookup(Iterator.class, "next", Object.class)); @@ -829,7 +932,19 @@ final class CodeGenerator extends NodeOperatorVisitor { method.label(breakLabel); } - return null; + return false; + } + + private static int assignSlots(final Block block, final int firstSlot) { + int nextSlot = firstSlot; + for (final Iterator<Symbol> iter = block.symbolIterator(); iter.hasNext(); ) { + final Symbol next = iter.next(); + if (next.hasSlot()) { + next.setSlot(nextSlot); + nextSlot += next.slotCount(); + } + } + return nextSlot; } /** @@ -838,21 +953,26 @@ final class CodeGenerator extends NodeOperatorVisitor { * @param block block with local vars. */ private void initLocals(final Block block) { - final FunctionNode function = lexicalContext.getFunction(block); - final boolean isFunctionNode = block == function; - - /* - * Get the symbols from the frame and realign the frame so that all - * slots get correct numbers. The slot numbering is not fixed until - * after initLocals has been run - */ - final Frame frame = block.getFrame(); - final List<Symbol> symbols = frame.getSymbols(); + final boolean isFunctionBody = getLexicalContext().isFunctionBody(); - /* Fix the predefined slots so they have numbers >= 0, like varargs. */ - frame.realign(); + final int nextFreeSlot; + if (isFunctionBody) { + // On entry to function, start with slot 0 + nextFreeSlot = 0; + } else { + // Otherwise, continue from previous block's first free slot + nextFreeSlot = nextFreeSlots[nextFreeSlotsSize - 1]; + } + if(nextFreeSlotsSize == nextFreeSlots.length) { + final int[] newNextFreeSlots = new int[nextFreeSlotsSize * 2]; + System.arraycopy(nextFreeSlots, 0, newNextFreeSlots, 0, nextFreeSlotsSize); + nextFreeSlots = newNextFreeSlots; + } + nextFreeSlots[nextFreeSlotsSize++] = assignSlots(block, nextFreeSlot); - if (isFunctionNode) { + final FunctionNode function = getLexicalContext().getCurrentFunction(); + if (isFunctionBody) { + /* Fix the predefined slots so they have numbers >= 0, like varargs. */ if (function.needsParentScope()) { initParentScope(); } @@ -876,14 +996,18 @@ final class CodeGenerator extends NodeOperatorVisitor { final List<String> nameList = new ArrayList<>(); final List<Symbol> locals = new ArrayList<>(); - // Initalize symbols and values final List<Symbol> newSymbols = new ArrayList<>(); final List<Symbol> values = new ArrayList<>(); final boolean hasArguments = function.needsArguments(); - for (final Symbol symbol : symbols) { - if (symbol.isInternal() || symbol.isThis()) { + + final Iterator<Symbol> symbols = block.symbolIterator(); + + while (symbols.hasNext()) { + final Symbol symbol = symbols.next(); + + if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) { continue; } @@ -907,9 +1031,6 @@ final class CodeGenerator extends NodeOperatorVisitor { } } - /* Correct slot numbering again */ - frame.realign(); - // we may have locals that need to be initialized initSymbols(locals); @@ -931,7 +1052,7 @@ final class CodeGenerator extends NodeOperatorVisitor { @Override protected void loadScope(MethodEmitter m) { if(function.needsParentScope()) { - m.loadScope(); + m.loadCompilerConstant(SCOPE); } else { m.loadNull(); } @@ -940,118 +1061,102 @@ final class CodeGenerator extends NodeOperatorVisitor { foc.makeObject(method); // runScript(): merge scope into global - if (isFunctionNode && function.isProgram()) { + if (isFunctionBody && function.isProgram()) { method.invoke(ScriptRuntime.MERGE_SCOPE); } - method.storeScope(); + method.storeCompilerConstant(SCOPE); } else { // Since we don't have a scope, parameters didn't get assigned array indices by the FieldObjectCreator, so // we need to assign them separately here. int nextParam = 0; - if (isFunctionNode && function.isVarArg()) { + if (isFunctionBody && function.isVarArg()) { for (final IdentNode param : function.getParameters()) { param.getSymbol().setFieldIndex(nextParam++); } } + + final Iterator<Symbol> iter = block.symbolIterator(); + final List<Symbol> symbols = new ArrayList<>(); + while (iter.hasNext()) { + symbols.add(iter.next()); + } initSymbols(symbols); } // Debugging: print symbols? @see --print-symbols flag - printSymbols(block, (isFunctionNode ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName())); + printSymbols(block, (isFunctionBody ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName())); } private void initArguments(final FunctionNode function) { - method.loadVarArgs(); + method.loadCompilerConstant(VARARGS); if(function.needsCallee()) { - method.loadCallee(); + method.loadCompilerConstant(CALLEE); } else { // If function is strict mode, "arguments.callee" is not populated, so we don't necessarily need the // caller. - assert function.isStrictMode(); + assert function.isStrict(); method.loadNull(); } method.load(function.getParameters().size()); globalAllocateArguments(); - method.storeArguments(); + method.storeCompilerConstant(ARGUMENTS); } private void initParentScope() { - method.loadCallee(); + method.loadCompilerConstant(CALLEE); method.invoke(ScriptFunction.GET_SCOPE); - method.storeScope(); + method.storeCompilerConstant(SCOPE); } @Override - public Node enterFunctionNode(final FunctionNode functionNode) { - final boolean isCallee = functionNodeIsCallee; - functionNodeIsCallee = false; - - if (functionNode.testResolved()) { - return null; - } - - if(!(isCallee || functionNode == compiler.getFunctionNode())) { - newFunctionObject(functionNode); - } - + public boolean enterFunctionNode(final FunctionNode functionNode) { if (functionNode.isLazy()) { - return null; + // Must do it now; can't postpone it until leaveFunctionNode() + newFunctionObject(functionNode, functionNode); + return false; } - LOG.info("=== BEGIN " + functionNode.getName()); - lexicalContext.push(functionNode); + LOG.info("=== BEGIN ", functionNode.getName()); - setCurrentCompileUnit(functionNode.getCompileUnit()); - assert getCurrentCompileUnit() != null; + assert functionNode.getCompileUnit() != null : "no compile unit for " + functionNode.getName() + " " + Debug.id(functionNode); + push(functionNode.getCompileUnit()); + assert !compileUnits.isEmpty(); - setCurrentMethodEmitter(getCurrentCompileUnit().getClassEmitter().method(functionNode)); - functionNode.setMethodEmitter(method); + pushMethodEmitter(unit.getClassEmitter().method(functionNode)); // Mark end for variable tables. method.begin(); - method.label(functionNode.getEntryLabel()); - initLocals(functionNode); - functionNode.setState(CompilationState.EMITTED); - - return functionNode; + return true; } @Override public Node leaveFunctionNode(final FunctionNode functionNode) { - // Mark end for variable tables. - method.label(functionNode.getBreakLabel()); - - if (!functionNode.needsScope()) { - method.markerVariable(LEAF.tag(), functionNode.getEntryLabel(), functionNode.getBreakLabel()); - } - - symbolInfo(functionNode); try { method.end(); // wrap up this method + pop(functionNode.getCompileUnit()); + popMethodEmitter(method); + LOG.info("=== END ", functionNode.getName()); + + final FunctionNode newFunctionNode = functionNode.setState(getLexicalContext(), CompilationState.EMITTED); + + newFunctionObject(newFunctionNode, functionNode); + return newFunctionNode; } catch (final Throwable t) { Context.printStackTrace(t); final VerifyError e = new VerifyError("Code generation bug in \"" + functionNode.getName() + "\": likely stack misaligned: " + t + " " + functionNode.getSource().getName()); e.initCause(t); throw e; } - - lexicalContext.pop(functionNode); - LOG.info("=== END " + functionNode.getName()); - return functionNode; } @Override - public Node enterIdentNode(final IdentNode identNode) { - return null; + public boolean enterIdentNode(final IdentNode identNode) { + return false; } @Override - public Node enterIfNode(final IfNode ifNode) { - if (ifNode.testResolved()) { - return null; - } - + public boolean enterIfNode(final IfNode ifNode) { final Node test = ifNode.getTest(); final Block pass = ifNode.getPass(); final Block fail = ifNode.getFail(); @@ -1082,30 +1187,21 @@ final class CodeGenerator extends NodeOperatorVisitor { method.label(afterLabel); } - return null; + return false; } @Override - public Node enterIndexNode(final IndexNode indexNode) { - if (indexNode.testResolved()) { - return null; - } - + public boolean enterIndexNode(final IndexNode indexNode) { load(indexNode); - - return null; + return false; } @Override - public Node enterLineNumberNode(final LineNumberNode lineNumberNode) { - if (lineNumberNode.testResolved()) { - return null; - } - - final Label label = new Label("line:" + lineNumberNode.getLineNumber() + " (" + getCurrentFunctionNode().getName() + ")"); + public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) { + final Label label = new Label((String)null); method.label(label); method.lineNumber(lineNumberNode.getLineNumber(), label); - return null; + return false; } /** @@ -1131,43 +1227,43 @@ final class CodeGenerator extends NodeOperatorVisitor { final Type elementType = arrayType.getElementType(); if (units != null) { - final CompileUnit savedCompileUnit = getCurrentCompileUnit(); - final MethodEmitter savedMethod = getCurrentMethodEmitter(); + final MethodEmitter savedMethod = method; - try { - for (final ArrayUnit unit : units) { - setCurrentCompileUnit(unit.getCompileUnit()); + for (final ArrayUnit arrayUnit : units) { + push(arrayUnit.getCompileUnit()); - final String className = getCurrentCompileUnit().getUnitClassName(); - final String name = getCurrentFunctionNode().uniqueName(SPLIT_PREFIX.tag()); - final String signature = methodDescriptor(type, Object.class, ScriptFunction.class, ScriptObject.class, type); + final String className = unit.getUnitClassName(); + final String name = getLexicalContext().getCurrentFunction().uniqueName(SPLIT_PREFIX.symbolName()); + final String signature = methodDescriptor(type, Object.class, ScriptFunction.class, ScriptObject.class, type); - setCurrentMethodEmitter(getCurrentCompileUnit().getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature)); - method.setFunctionNode(getCurrentFunctionNode()); - method.begin(); + final MethodEmitter me = unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature); + pushMethodEmitter(me); - fixScopeSlot(); + method.setFunctionNode(getLexicalContext().getCurrentFunction()); + method.begin(); - method.load(arrayType, SPLIT_ARRAY_ARG.slot()); + fixScopeSlot(); - for (int i = unit.getLo(); i < unit.getHi(); i++) { - storeElement(nodes, elementType, postsets[i]); - } - - method._return(); - method.end(); + method.load(arrayType, SPLIT_ARRAY_ARG.slot()); - savedMethod.loadThis(); - savedMethod.swap(); - savedMethod.loadCallee(); - savedMethod.swap(); - savedMethod.loadScope(); - savedMethod.swap(); - savedMethod.invokestatic(className, name, signature); + for (int i = arrayUnit.getLo(); i < arrayUnit.getHi(); i++) { + storeElement(nodes, elementType, postsets[i]); } - } finally { - setCurrentCompileUnit(savedCompileUnit); - setCurrentMethodEmitter(savedMethod); + + method._return(); + method.end(); + popMethodEmitter(me); + + assert method == savedMethod; + method.loadCompilerConstant(THIS); + method.swap(); + method.loadCompilerConstant(CALLEE); + method.swap(); + method.loadCompilerConstant(SCOPE); + method.swap(); + method.invokestatic(className, name, signature); + + pop(unit); } return method; @@ -1217,12 +1313,12 @@ final class CodeGenerator extends NodeOperatorVisitor { * @param string string to load */ void loadConstant(final String string) { - final String unitClassName = getCurrentCompileUnit().getUnitClassName(); - final ClassEmitter classEmitter = getCurrentCompileUnit().getClassEmitter(); + final String unitClassName = unit.getUnitClassName(); + final ClassEmitter classEmitter = unit.getClassEmitter(); final int index = compiler.getConstantData().add(string); method.load(index); - method.invokestatic(unitClassName, GET_STRING.tag(), methodDescriptor(String.class, int.class)); + method.invokestatic(unitClassName, GET_STRING.symbolName(), methodDescriptor(String.class, int.class)); classEmitter.needGetConstantMethod(String.class); } @@ -1233,14 +1329,14 @@ final class CodeGenerator extends NodeOperatorVisitor { * @param object object to load */ void loadConstant(final Object object) { - final String unitClassName = getCurrentCompileUnit().getUnitClassName(); - final ClassEmitter classEmitter = getCurrentCompileUnit().getClassEmitter(); + final String unitClassName = unit.getUnitClassName(); + final ClassEmitter classEmitter = unit.getClassEmitter(); final int index = compiler.getConstantData().add(object); final Class<?> cls = object.getClass(); if (cls == PropertyMap.class) { method.load(index); - method.invokestatic(unitClassName, GET_MAP.tag(), methodDescriptor(PropertyMap.class, int.class)); + method.invokestatic(unitClassName, GET_MAP.symbolName(), methodDescriptor(PropertyMap.class, int.class)); classEmitter.needGetConstantMethod(PropertyMap.class); } else if (cls.isArray()) { method.load(index); @@ -1303,14 +1399,14 @@ final class CodeGenerator extends NodeOperatorVisitor { return loadRegexToken(regexToken); } // emit field - final String regexName = getCurrentFunctionNode().uniqueName(REGEX_PREFIX.tag()); - final ClassEmitter classEmitter = getCurrentCompileUnit().getClassEmitter(); + final String regexName = getLexicalContext().getCurrentFunction().uniqueName(REGEX_PREFIX.symbolName()); + final ClassEmitter classEmitter = unit.getClassEmitter(); classEmitter.field(EnumSet.of(PRIVATE, STATIC), regexName, Object.class); regexFieldCount++; // get field, if null create new regex, finally clone regex object - method.getStatic(getCurrentCompileUnit().getUnitClassName(), regexName, typeDescriptor(Object.class)); + method.getStatic(unit.getUnitClassName(), regexName, typeDescriptor(Object.class)); method.dup(); final Label cachedLabel = new Label("cached"); method.ifnonnull(cachedLabel); @@ -1318,7 +1414,7 @@ final class CodeGenerator extends NodeOperatorVisitor { method.pop(); loadRegexToken(regexToken); method.dup(); - method.putStatic(getCurrentCompileUnit().getUnitClassName(), regexName, typeDescriptor(Object.class)); + method.putStatic(unit.getUnitClassName(), regexName, typeDescriptor(Object.class)); method.label(cachedLabel); globalRegExpCopy(); @@ -1328,18 +1424,14 @@ final class CodeGenerator extends NodeOperatorVisitor { @SuppressWarnings("rawtypes") @Override - public Node enterLiteralNode(final LiteralNode literalNode) { + public boolean enterLiteralNode(final LiteralNode literalNode) { assert literalNode.getSymbol() != null : literalNode + " has no symbol"; load(literalNode).store(literalNode.getSymbol()); - return null; + return false; } @Override - public Node enterObjectNode(final ObjectNode objectNode) { - if (objectNode.testResolved()) { - return null; - } - + public boolean enterObjectNode(final ObjectNode objectNode) { final List<Node> elements = objectNode.getElements(); final int size = elements.size(); @@ -1404,14 +1496,14 @@ final class CodeGenerator extends NodeOperatorVisitor { if (!hasGettersSetters) { method.store(objectNode.getSymbol()); - return null; + return false; } for (final Node element : elements) { final PropertyNode propertyNode = (PropertyNode)element; final Object key = propertyNode.getKey(); - final FunctionNode getter = (FunctionNode)propertyNode.getGetter(); - final FunctionNode setter = (FunctionNode)propertyNode.getSetter(); + final FunctionNode getter = propertyNode.getGetter(); + final FunctionNode setter = propertyNode.getSetter(); if (getter == null && setter == null) { continue; @@ -1436,35 +1528,25 @@ final class CodeGenerator extends NodeOperatorVisitor { method.store(objectNode.getSymbol()); - return null; + return false; } @Override - public Node enterReturnNode(final ReturnNode returnNode) { - if (returnNode.testResolved()) { - return null; - } - - // Set the split return flag in the scope if this is a split method fragment. - if (method.getSplitNode() != null) { - assert method.getSplitNode().hasReturn() : "unexpected return in split node"; + public boolean enterReturnNode(final ReturnNode returnNode) { + method.registerReturn(); - method.loadScope(); - method.checkcast(Scope.class); - method.load(0); - method.invoke(Scope.SET_SPLIT_STATE); - } + final Type returnType = getLexicalContext().getCurrentFunction().getReturnType(); final Node expression = returnNode.getExpression(); if (expression != null) { load(expression); } else { - method.loadUndefined(getCurrentFunctionNode().getReturnType()); + method.loadUndefined(returnType); } - method._return(getCurrentFunctionNode().getReturnType()); + method._return(returnType); - return null; + return false; } private static boolean isNullLiteral(final Node node) { @@ -1542,19 +1624,20 @@ final class CodeGenerator extends NodeOperatorVisitor { } assert args.size() == 2; - final Node lhs = args.get(0); - final Node rhs = args.get(1); - final Type returnType = node.getType(); - load(lhs); - load(rhs); + + load(args.get(0)); + load(args.get(1)); Request finalRequest = request; + //if the request is a comparison, i.e. one that can be reversed + //it keeps its semantic, but make sure that the object comes in + //last final Request reverse = Request.reverse(request); - if (method.peekType().isObject() && reverse != null) { - if (!method.peekType(1).isObject()) { - method.swap(); + if (method.peekType().isObject() && reverse != null) { //rhs is object + if (!method.peekType(1).isObject()) { //lhs is not object + method.swap(); //prefer object as lhs finalRequest = reverse; } } @@ -1581,11 +1664,7 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterRuntimeNode(final RuntimeNode runtimeNode) { - if (runtimeNode.testResolved()) { - return null; - } - + public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { /* * First check if this should be something other than a runtime node * AccessSpecializer might have changed the type @@ -1624,7 +1703,7 @@ final class CodeGenerator extends NodeOperatorVisitor { method.add(); method.convert(type); method.store(symbol); - return null; + return false; default: // it's ok to send this one on with only primitive arguments, maybe INSTANCEOF(true, true) or similar // assert false : runtimeNode + " has all primitive arguments. This is an inconsistent state"; @@ -1636,11 +1715,11 @@ final class CodeGenerator extends NodeOperatorVisitor { final List<Node> args = runtimeNode.getArgs(); if (nullCheck(runtimeNode, args, new FunctionSignature(false, false, runtimeNode.getType(), args).toString())) { - return null; + return false; } if (!runtimeNode.isFinal() && specializationCheck(runtimeNode.getRequest(), runtimeNode, args)) { - return null; + return false; } for (final Node arg : runtimeNode.getArgs()) { @@ -1658,129 +1737,146 @@ final class CodeGenerator extends NodeOperatorVisitor { method.convert(runtimeNode.getType()); method.store(runtimeNode.getSymbol()); - return null; + return false; } @Override - public Node enterSplitNode(final SplitNode splitNode) { - if (splitNode.testResolved()) { - return null; - } - + public boolean enterSplitNode(final SplitNode splitNode) { final CompileUnit splitCompileUnit = splitNode.getCompileUnit(); - final FunctionNode fn = getCurrentFunctionNode(); + final FunctionNode fn = getLexicalContext().getCurrentFunction(); final String className = splitCompileUnit.getUnitClassName(); final String name = splitNode.getName(); - final Class<?> rtype = fn.getReturnType().getTypeClass(); - final boolean needsArguments = fn.needsArguments(); - final Class<?>[] ptypes = needsArguments ? + final Class<?> rtype = fn.getReturnType().getTypeClass(); + final boolean needsArguments = fn.needsArguments(); + final Class<?>[] ptypes = needsArguments ? new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class, Object.class} : new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class}; - setCurrentCompileUnit(splitCompileUnit); - splitNode.setCompileUnit(splitCompileUnit); + final MethodEmitter caller = method; + push(splitCompileUnit); final Call splitCall = staticCallNoLookup( className, name, methodDescriptor(rtype, ptypes)); - setCurrentMethodEmitter( - splitCompileUnit.getClassEmitter().method( - EnumSet.of(Flag.PUBLIC, Flag.STATIC), - name, - rtype, - ptypes)); + final MethodEmitter splitEmitter = + splitCompileUnit.getClassEmitter().method( + splitNode, + name, + rtype, + ptypes); + + pushMethodEmitter(splitEmitter); method.setFunctionNode(fn); - method.setSplitNode(splitNode); - splitNode.setMethodEmitter(method); - final MethodEmitter caller = splitNode.getCaller(); - if(fn.needsCallee()) { - caller.loadCallee(); + if (fn.needsCallee()) { + caller.loadCompilerConstant(CALLEE); } else { caller.loadNull(); } - caller.loadThis(); - caller.loadScope(); + caller.loadCompilerConstant(THIS); + caller.loadCompilerConstant(SCOPE); if (needsArguments) { - caller.loadArguments(); + caller.loadCompilerConstant(ARGUMENTS); } caller.invoke(splitCall); - caller.storeResult(); + caller.storeCompilerConstant(RETURN); method.begin(); method.loadUndefined(fn.getReturnType()); - method.storeResult(); + method.storeCompilerConstant(RETURN); fixScopeSlot(); - return splitNode; + return true; } private void fixScopeSlot() { - if (getCurrentFunctionNode().getScopeNode().getSymbol().getSlot() != SCOPE.slot()) { + if (getLexicalContext().getCurrentFunction().compilerConstant(SCOPE).getSlot() != SCOPE.slot()) { // TODO hack to move the scope to the expected slot (that's needed because split methods reuse the same slots as the root method) method.load(Type.typeFor(ScriptObject.class), SCOPE.slot()); - method.storeScope(); + method.storeCompilerConstant(SCOPE); } } @Override public Node leaveSplitNode(final SplitNode splitNode) { + assert method instanceof SplitMethodEmitter; + final boolean hasReturn = method.hasReturn(); + final List<Label> targets = method.getExternalTargets(); + try { // Wrap up this method. - method.loadResult(); - method._return(getCurrentFunctionNode().getReturnType()); + + method.loadCompilerConstant(RETURN); + method._return(getLexicalContext().getCurrentFunction().getReturnType()); method.end(); + + pop(splitNode.getCompileUnit()); + popMethodEmitter(method); + } catch (final Throwable t) { Context.printStackTrace(t); - final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + getCurrentFunctionNode().getSource().getName()); + final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + getLexicalContext().getCurrentFunction().getSource().getName()); e.initCause(t); throw e; } // Handle return from split method if there was one. - final MethodEmitter caller = splitNode.getCaller(); - final List<Label> targets = splitNode.getExternalTargets(); - final int targetCount = targets.size(); + final MethodEmitter caller = method; + final int targetCount = targets.size(); + + //no external jump targets or return in switch node + if (!hasReturn && targets.isEmpty()) { + return splitNode; + } + + caller.loadCompilerConstant(SCOPE); + caller.checkcast(Scope.class); + caller.invoke(Scope.GET_SPLIT_STATE); - if (splitNode.hasReturn() || targetCount > 0) { + final Label breakLabel = new Label("no_split_state"); + // Split state is -1 for no split state, 0 for return, 1..n+1 for break/continue - caller.loadScope(); - caller.checkcast(Scope.class); - caller.invoke(Scope.GET_SPLIT_STATE); + //the common case is that we don't need a switch + if (targetCount == 0) { + assert hasReturn; + caller.ifne(breakLabel); + //has to be zero + caller.label(new Label("split_return")); + method.loadCompilerConstant(RETURN); + caller._return(getLexicalContext().getCurrentFunction().getReturnType()); + caller.label(breakLabel); + } else { + assert !targets.isEmpty(); - // Split state is -1 for no split state, 0 for return, 1..n+1 for break/continue - final Label breakLabel = new Label("no_split_state"); - final int low = splitNode.hasReturn() ? 0 : 1; - final int labelCount = targetCount + 1 - low; - final Label[] labels = new Label[labelCount]; + final int low = hasReturn ? 0 : 1; + final int labelCount = targetCount + 1 - low; + final Label[] labels = new Label[labelCount]; for (int i = 0; i < labelCount; i++) { - labels[i] = new Label("split_state_" + i); + labels[i] = new Label(i == 0 ? "split_return" : "split_" + targets.get(i - 1)); } - caller.tableswitch(low, targetCount, breakLabel, labels); for (int i = low; i <= targetCount; i++) { caller.label(labels[i - low]); if (i == 0) { - caller.loadResult(); - caller._return(getCurrentFunctionNode().getReturnType()); + caller.loadCompilerConstant(RETURN); + caller._return(getLexicalContext().getCurrentFunction().getReturnType()); } else { // Clear split state. - caller.loadScope(); + caller.loadCompilerConstant(SCOPE); caller.checkcast(Scope.class); caller.load(-1); caller.invoke(Scope.SET_SPLIT_STATE); - caller.splitAwareGoto(targets.get(i - 1)); + caller.splitAwareGoto(getLexicalContext(), targets.get(i - 1)); } } - caller.label(breakLabel); } @@ -1788,11 +1884,7 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterSwitchNode(final SwitchNode switchNode) { - if (switchNode.testResolved()) { - return null; - } - + public boolean enterSwitchNode(final SwitchNode switchNode) { final Node expression = switchNode.getExpression(); final Symbol tag = switchNode.getTag(); final boolean allInteger = tag.getSymbolType().isInteger(); @@ -1810,7 +1902,7 @@ final class CodeGenerator extends NodeOperatorVisitor { if (cases.isEmpty()) { method.label(breakLabel); - return null; + return false; } if (allInteger) { @@ -1916,15 +2008,11 @@ final class CodeGenerator extends NodeOperatorVisitor { method.label(breakLabel); } - return null; + return false; } @Override - public Node enterThrowNode(final ThrowNode throwNode) { - if (throwNode.testResolved()) { - return null; - } - + public boolean enterThrowNode(final ThrowNode throwNode) { method._new(ECMAException.class).dup(); final Node expression = throwNode.getExpression(); @@ -1943,15 +2031,11 @@ final class CodeGenerator extends NodeOperatorVisitor { method.athrow(); - return null; + return false; } @Override - public Node enterTryNode(final TryNode tryNode) { - if (tryNode.testResolved()) { - return null; - } - + public boolean enterTryNode(final TryNode tryNode) { final Block body = tryNode.getBody(); final List<Block> catchBlocks = tryNode.getCatchBlocks(); final Symbol symbol = tryNode.getException(); @@ -1974,74 +2058,68 @@ final class CodeGenerator extends NodeOperatorVisitor { method.store(symbol); for (int i = 0; i < catchBlocks.size(); i++) { - final Block saveBlock = getCurrentBlock(); final Block catchBlock = catchBlocks.get(i); - setCurrentBlock(catchBlock); - - try { - enterBlock(catchBlock); + //TODO this is very ugly - try not to call enter/leave methods directly + //better to use the implicit lexical context scoping given by the visitor's + //accept method. + getLexicalContext().push(catchBlock); + enterBlock(catchBlock); - final CatchNode catchNode = (CatchNode)catchBlocks.get(i).getStatements().get(0); - final IdentNode exception = catchNode.getException(); - final Node exceptionCondition = catchNode.getExceptionCondition(); - final Block catchBody = catchNode.getBody(); + final CatchNode catchNode = (CatchNode)catchBlocks.get(i).getStatements().get(0); + final IdentNode exception = catchNode.getException(); + final Node exceptionCondition = catchNode.getExceptionCondition(); + final Block catchBody = catchNode.getBody(); - if (catchNode.isSyntheticRethrow()) { - // Generate catch body (inlined finally) and rethrow exception - catchBody.accept(this); - method.load(symbol).athrow(); - lexicalContext.pop(catchBlock); - continue; + new Store<IdentNode>(exception) { + @Override + protected void storeNonDiscard() { + return; + } + @Override + protected void evaluate() { + /* + * If caught object is an instance of ECMAException, then + * bind obj.thrown to the script catch var. Or else bind the + * caught object itself to the script catch var. + */ + final Label notEcmaException = new Label("no_ecma_exception"); + + method.load(symbol).dup()._instanceof(ECMAException.class).ifeq(notEcmaException); + method.checkcast(ECMAException.class); //TODO is this necessary? + method.getField(ECMAException.THROWN); + method.label(notEcmaException); } + }.store(); - new Store<IdentNode>(exception) { - @Override - protected void evaluate() { - /* - * If caught object is an instance of ECMAException, then - * bind obj.thrown to the script catch var. Or else bind the - * caught object itself to the script catch var. - */ - final Label notEcmaException = new Label("no_ecma_exception"); - - method.load(symbol).dup()._instanceof(ECMAException.class).ifeq(notEcmaException); - method.checkcast(ECMAException.class); //TODO is this necessary? - method.getField(ECMAException.THROWN); - method.label(notEcmaException); - } - }.store(); + final Label next; - final Label next; + if (exceptionCondition != null) { + next = new Label("next"); + load(exceptionCondition).convert(Type.BOOLEAN).ifeq(next); + } else { + next = null; + } - if (exceptionCondition != null) { - next = new Label("next"); - load(exceptionCondition).convert(Type.BOOLEAN).ifeq(next); - } else { - next = null; - } + catchBody.accept(this); - catchBody.accept(this); + if (i + 1 != catchBlocks.size() && !catchBody.hasTerminalFlags()) { + method._goto(skip); + } - if (i + 1 != catchBlocks.size() && !catchBody.hasTerminalFlags()) { + if (next != null) { + if (i + 1 == catchBlocks.size()) { + // no next catch block - rethrow if condition failed method._goto(skip); + method.label(next); + method.load(symbol).athrow(); + } else { + method.label(next); } - - if (next != null) { - if (i + 1 == catchBlocks.size()) { - // no next catch block - rethrow if condition failed - method._goto(skip); - method.label(next); - method.load(symbol).athrow(); - } else { - method.label(next); - } - } - - leaveBlock(catchBlock); - } finally { - setCurrentBlock(saveBlock); } + + leaveBlock(catchBlock); + getLexicalContext().pop(catchBlock); } method.label(skip); @@ -2049,15 +2127,15 @@ final class CodeGenerator extends NodeOperatorVisitor { // Finally body is always inlined elsewhere so it doesn't need to be emitted - return null; + return false; } @Override - public Node enterVarNode(final VarNode varNode) { + public boolean enterVarNode(final VarNode varNode) { final Node init = varNode.getInit(); - if (varNode.testResolved() || init == null) { - return null; + if (init == null) { + return false; } final Symbol varSymbol = varNode.getSymbol(); @@ -2067,7 +2145,7 @@ final class CodeGenerator extends NodeOperatorVisitor { final boolean needsScope = varSymbol.isScope(); if (needsScope) { - method.loadScope(); + method.loadCompilerConstant(SCOPE); } load(init); @@ -2087,22 +2165,18 @@ final class CodeGenerator extends NodeOperatorVisitor { method.store(varSymbol); } - return null; + return false; } @Override - public Node enterWhileNode(final WhileNode whileNode) { - if (whileNode.testResolved()) { - return null; - } - + public boolean enterWhileNode(final WhileNode whileNode) { final Node test = whileNode.getTest(); final Block body = whileNode.getBody(); final Label breakLabel = whileNode.getBreakLabel(); final Label continueLabel = whileNode.getContinueLabel(); final Label loopLabel = new Label("loop"); - if (!(whileNode instanceof DoWhileNode)) { + if (!whileNode.isDoWhile()) { method._goto(continueLabel); } @@ -2114,90 +2188,95 @@ final class CodeGenerator extends NodeOperatorVisitor { method.label(breakLabel); } - return null; + return false; } private void closeWith() { - method.loadScope(); - method.invoke(ScriptRuntime.CLOSE_WITH); - method.storeScope(); + if(method.hasScope()) { + method.loadCompilerConstant(SCOPE); + method.invoke(ScriptRuntime.CLOSE_WITH); + method.storeCompilerConstant(SCOPE); + } } @Override - public Node enterWithNode(final WithNode withNode) { - if (withNode.testResolved()) { - return null; - } - + public boolean enterWithNode(final WithNode withNode) { final Node expression = withNode.getExpression(); final Node body = withNode.getBody(); - final Label tryLabel = new Label("with_try"); - final Label endLabel = new Label("with_end"); - final Label catchLabel = new Label("with_catch"); - final Label exitLabel = new Label("with_exit"); - - method.label(tryLabel); + // It is possible to have a "pathological" case where the with block does not reference *any* identifiers. It's + // pointless, but legal. In that case, if nothing else in the method forced the assignment of a slot to the + // scope object, its' possible that it won't have a slot assigned. In this case we'll only evaluate expression + // for its side effect and visit the body, and not bother opening and closing a WithObject. + final boolean hasScope = method.hasScope(); + + final Label tryLabel; + if(hasScope) { + tryLabel = new Label("with_try"); + method.label(tryLabel); + method.loadCompilerConstant(SCOPE); + } else { + tryLabel = null; + } - method.loadScope(); load(expression); - assert expression.getType().isObject() : "with expression needs to be object: " + expression; - method.invoke(ScriptRuntime.OPEN_WITH); - method.storeScope(); + if(hasScope) { + // Construct a WithObject if we have a scope + method.invoke(ScriptRuntime.OPEN_WITH); + method.storeCompilerConstant(SCOPE); + } else { + // We just loaded the expression for its side effect; discard it + method.pop(); + } + + // Always process body body.accept(this); - if (!body.isTerminal()) { - closeWith(); - method._goto(exitLabel); - } + if(hasScope) { + // Ensure we always close the WithObject + final Label endLabel = new Label("with_end"); + final Label catchLabel = new Label("with_catch"); + final Label exitLabel = new Label("with_exit"); - method.label(endLabel); + if (!body.isTerminal()) { + closeWith(); + method._goto(exitLabel); + } - method._catch(catchLabel); - closeWith(); - method.athrow(); + method.label(endLabel); - method.label(exitLabel); + method._catch(catchLabel); + closeWith(); + method.athrow(); - method._try(tryLabel, endLabel, catchLabel); + method.label(exitLabel); - return null; + method._try(tryLabel, endLabel, catchLabel); + } + return false; } @Override - public Node enterADD(final UnaryNode unaryNode) { - if (unaryNode.testResolved()) { - return null; - } - + public boolean enterADD(final UnaryNode unaryNode) { load(unaryNode.rhs()); assert unaryNode.rhs().getType().isNumber(); method.store(unaryNode.getSymbol()); - return null; + return false; } @Override - public Node enterBIT_NOT(final UnaryNode unaryNode) { - if (unaryNode.testResolved()) { - return null; - } - + public boolean enterBIT_NOT(final UnaryNode unaryNode) { load(unaryNode.rhs()).convert(Type.INT).load(-1).xor().store(unaryNode.getSymbol()); - - return null; + return false; } // do this better with convert calls to method. TODO @Override - public Node enterCONVERT(final UnaryNode unaryNode) { - if (unaryNode.testResolved()) { - return null; - } - + public boolean enterCONVERT(final UnaryNode unaryNode) { final Node rhs = unaryNode.rhs(); final Type to = unaryNode.getType(); @@ -2230,15 +2309,11 @@ final class CodeGenerator extends NodeOperatorVisitor { method.store(unaryNode.getSymbol()); - return null; + return false; } @Override - public Node enterDECINC(final UnaryNode unaryNode) { - if (unaryNode.testResolved()) { - return null; - } - + public boolean enterDECINC(final UnaryNode unaryNode) { final Node rhs = unaryNode.rhs(); final Type type = unaryNode.getType(); final TokenType tokenType = unaryNode.tokenType(); @@ -2282,32 +2357,28 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterDISCARD(final UnaryNode unaryNode) { - if (unaryNode.testResolved()) { - return null; - } - + public boolean enterDISCARD(final UnaryNode unaryNode) { final Node rhs = unaryNode.rhs(); + // System.err.println("**** Enter discard " + unaryNode); + discard.push(rhs); load(rhs); - if (rhs.shouldDiscard()) { + if (discard.peek() == rhs) { + assert !rhs.isAssignment(); method.pop(); + discard.pop(); } - - return null; + // System.err.println("**** Leave discard " + unaryNode); + return false; } @Override - public Node enterNEW(final UnaryNode unaryNode) { - if (unaryNode.testResolved()) { - return null; - } - + public boolean enterNEW(final UnaryNode unaryNode) { final CallNode callNode = (CallNode)unaryNode.rhs(); final List<Node> args = callNode.getArgs(); @@ -2317,15 +2388,11 @@ final class CodeGenerator extends NodeOperatorVisitor { method.dynamicNew(1 + loadArgs(args), getCallSiteFlags()); method.store(unaryNode.getSymbol()); - return null; + return false; } @Override - public Node enterNOT(final UnaryNode unaryNode) { - if (unaryNode.testResolved()) { - return null; - } - + public boolean enterNOT(final UnaryNode unaryNode) { final Node rhs = unaryNode.rhs(); load(rhs); @@ -2342,18 +2409,14 @@ final class CodeGenerator extends NodeOperatorVisitor { method.label(afterLabel); method.store(unaryNode.getSymbol()); - return null; + return false; } @Override - public Node enterSUB(final UnaryNode unaryNode) { - if (unaryNode.testResolved()) { - return null; - } - + public boolean enterSUB(final UnaryNode unaryNode) { load(unaryNode.rhs()).neg().store(unaryNode.getSymbol()); - return null; + return false; } private Node enterNumericAdd(final Node lhs, final Node rhs, final Type type, final Symbol symbol) { @@ -2366,11 +2429,7 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterADD(final BinaryNode binaryNode) { - if (binaryNode.testResolved()) { - return null; - } - + public boolean enterADD(final BinaryNode binaryNode) { final Node lhs = binaryNode.lhs(); final Node rhs = binaryNode.rhs(); @@ -2384,14 +2443,10 @@ final class CodeGenerator extends NodeOperatorVisitor { method.store(binaryNode.getSymbol()); } - return null; + return false; } - private Node enterAND_OR(final BinaryNode binaryNode) { - if (binaryNode.testResolved()) { - return null; - } - + private boolean enterAND_OR(final BinaryNode binaryNode) { final Node lhs = binaryNode.lhs(); final Node rhs = binaryNode.rhs(); @@ -2410,20 +2465,16 @@ final class CodeGenerator extends NodeOperatorVisitor { method.label(skip); method.store(binaryNode.getSymbol()); - return null; + return false; } @Override - public Node enterAND(final BinaryNode binaryNode) { + public boolean enterAND(final BinaryNode binaryNode) { return enterAND_OR(binaryNode); } @Override - public Node enterASSIGN(final BinaryNode binaryNode) { - if (binaryNode.testResolved()) { - return null; - } - + public boolean enterASSIGN(final BinaryNode binaryNode) { final Node lhs = binaryNode.lhs(); final Node rhs = binaryNode.rhs(); @@ -2442,7 +2493,7 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } /** @@ -2473,14 +2524,6 @@ final class CodeGenerator extends NodeOperatorVisitor { this.opType = opType; } - @Override - public void store() { - if (assignNode.testResolved()) { - return; - } - super.store(); - } - protected abstract void op(); @Override @@ -2493,35 +2536,43 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterASSIGN_ADD(final BinaryNode binaryNode) { + public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { assert RuntimeNode.Request.ADD.canSpecialize(); + final Type lhsType = binaryNode.lhs().getType(); + final Type rhsType = binaryNode.rhs().getType(); final boolean specialize = binaryNode.getType() == Type.OBJECT; new AssignOp(binaryNode) { - @Override - protected boolean isSelfModifying() { - return !specialize; - } @Override protected void op() { - method.add(); + if (specialize) { + method.dynamicRuntimeCall( + new SpecializedRuntimeNode( + Request.ADD, + new Type[] { + lhsType, + rhsType, + }, + Type.OBJECT).getInitialName(), + Type.OBJECT, + Request.ADD); + } else { + method.add(); + } } @Override protected void evaluate() { - if (specialize && specializationCheck(Request.ADD, assignNode, Arrays.asList(assignNode.lhs(), assignNode.rhs()))) { - return; - } super.evaluate(); } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_BIT_AND(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { new AssignOp(Type.INT, binaryNode) { @Override protected void op() { @@ -2529,11 +2580,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_BIT_OR(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { new AssignOp(Type.INT, binaryNode) { @Override protected void op() { @@ -2541,11 +2592,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { new AssignOp(Type.INT, binaryNode) { @Override protected void op() { @@ -2553,11 +2604,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_DIV(final BinaryNode binaryNode) { + public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { new AssignOp(binaryNode) { @Override protected void op() { @@ -2565,11 +2616,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_MOD(final BinaryNode binaryNode) { + public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { new AssignOp(binaryNode) { @Override protected void op() { @@ -2577,11 +2628,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_MUL(final BinaryNode binaryNode) { + public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { new AssignOp(binaryNode) { @Override protected void op() { @@ -2589,11 +2640,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_SAR(final BinaryNode binaryNode) { + public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { new AssignOp(Type.INT, binaryNode) { @Override protected void op() { @@ -2601,11 +2652,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_SHL(final BinaryNode binaryNode) { + public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { new AssignOp(Type.INT, binaryNode) { @Override protected void op() { @@ -2613,24 +2664,24 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_SHR(final BinaryNode binaryNode) { + public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { new AssignOp(Type.INT, binaryNode) { @Override protected void op() { method.shr(); - method.convert(Type.LONG).load(0xffff_ffffL).and(); + method.convert(Type.LONG).load(JSType.MAX_UINT).and(); } }.store(); - return null; + return false; } @Override - public Node enterASSIGN_SUB(final BinaryNode binaryNode) { + public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { new AssignOp(binaryNode) { @Override protected void op() { @@ -2638,7 +2689,7 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.store(); - return null; + return false; } /** @@ -2649,9 +2700,6 @@ final class CodeGenerator extends NodeOperatorVisitor { protected abstract void op(); protected void evaluate(final BinaryNode node) { - if (node.testResolved()) { - return; - } load(node.lhs()); load(node.rhs()); op(); @@ -2660,7 +2708,7 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterBIT_AND(final BinaryNode binaryNode) { + public boolean enterBIT_AND(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2668,11 +2716,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } @Override - public Node enterBIT_OR(final BinaryNode binaryNode) { + public boolean enterBIT_OR(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2680,11 +2728,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } @Override - public Node enterBIT_XOR(final BinaryNode binaryNode) { + public boolean enterBIT_XOR(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2692,14 +2740,10 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } - private Node enterComma(final BinaryNode binaryNode) { - if (binaryNode.testResolved()) { - return null; - } - + private boolean enterComma(final BinaryNode binaryNode) { final Node lhs = binaryNode.lhs(); final Node rhs = binaryNode.rhs(); @@ -2707,21 +2751,21 @@ final class CodeGenerator extends NodeOperatorVisitor { load(rhs); method.store(binaryNode.getSymbol()); - return null; + return false; } @Override - public Node enterCOMMARIGHT(final BinaryNode binaryNode) { + public boolean enterCOMMARIGHT(final BinaryNode binaryNode) { return enterComma(binaryNode); } @Override - public Node enterCOMMALEFT(final BinaryNode binaryNode) { + public boolean enterCOMMALEFT(final BinaryNode binaryNode) { return enterComma(binaryNode); } @Override - public Node enterDIV(final BinaryNode binaryNode) { + public boolean enterDIV(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2729,10 +2773,10 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } - private Node enterCmp(final Node lhs, final Node rhs, final Condition cond, final Type type, final Symbol symbol) { + private boolean enterCmp(final Node lhs, final Node rhs, final Condition cond, final Type type, final Symbol symbol) { final Type lhsType = lhs.getType(); final Type rhsType = rhs.getType(); @@ -2758,48 +2802,45 @@ final class CodeGenerator extends NodeOperatorVisitor { method.convert(type); method.store(symbol); - return null; + return false; } - private Node enterCmp(final BinaryNode binaryNode, final Condition cond) { - if (binaryNode.testResolved()) { - return null; - } + private boolean enterCmp(final BinaryNode binaryNode, final Condition cond) { return enterCmp(binaryNode.lhs(), binaryNode.rhs(), cond, binaryNode.getType(), binaryNode.getSymbol()); } @Override - public Node enterEQ(final BinaryNode binaryNode) { + public boolean enterEQ(final BinaryNode binaryNode) { return enterCmp(binaryNode, Condition.EQ); } @Override - public Node enterEQ_STRICT(final BinaryNode binaryNode) { + public boolean enterEQ_STRICT(final BinaryNode binaryNode) { return enterCmp(binaryNode, Condition.EQ); } @Override - public Node enterGE(final BinaryNode binaryNode) { + public boolean enterGE(final BinaryNode binaryNode) { return enterCmp(binaryNode, Condition.GE); } @Override - public Node enterGT(final BinaryNode binaryNode) { + public boolean enterGT(final BinaryNode binaryNode) { return enterCmp(binaryNode, Condition.GT); } @Override - public Node enterLE(final BinaryNode binaryNode) { + public boolean enterLE(final BinaryNode binaryNode) { return enterCmp(binaryNode, Condition.LE); } @Override - public Node enterLT(final BinaryNode binaryNode) { + public boolean enterLT(final BinaryNode binaryNode) { return enterCmp(binaryNode, Condition.LT); } @Override - public Node enterMOD(final BinaryNode binaryNode) { + public boolean enterMOD(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2807,11 +2848,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } @Override - public Node enterMUL(final BinaryNode binaryNode) { + public boolean enterMUL(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2819,26 +2860,26 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } @Override - public Node enterNE(final BinaryNode binaryNode) { + public boolean enterNE(final BinaryNode binaryNode) { return enterCmp(binaryNode, Condition.NE); } @Override - public Node enterNE_STRICT(final BinaryNode binaryNode) { + public boolean enterNE_STRICT(final BinaryNode binaryNode) { return enterCmp(binaryNode, Condition.NE); } @Override - public Node enterOR(final BinaryNode binaryNode) { + public boolean enterOR(final BinaryNode binaryNode) { return enterAND_OR(binaryNode); } @Override - public Node enterSAR(final BinaryNode binaryNode) { + public boolean enterSAR(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2846,11 +2887,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } @Override - public Node enterSHL(final BinaryNode binaryNode) { + public boolean enterSHL(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2858,24 +2899,24 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } @Override - public Node enterSHR(final BinaryNode binaryNode) { + public boolean enterSHR(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { method.shr(); - method.convert(Type.LONG).load(0xffff_ffffL).and(); + method.convert(Type.LONG).load(JSType.MAX_UINT).and(); } }.evaluate(binaryNode); - return null; + return false; } @Override - public Node enterSUB(final BinaryNode binaryNode) { + public boolean enterSUB(final BinaryNode binaryNode) { new BinaryArith() { @Override protected void op() { @@ -2883,18 +2924,11 @@ final class CodeGenerator extends NodeOperatorVisitor { } }.evaluate(binaryNode); - return null; + return false; } - /* - * Ternary visits. - */ @Override - public Node enterTernaryNode(final TernaryNode ternaryNode) { - if (ternaryNode.testResolved()) { - return null; - } - + public boolean enterTernaryNode(final TernaryNode ternaryNode) { final Node lhs = ternaryNode.lhs(); final Node rhs = ternaryNode.rhs(); final Node third = ternaryNode.third(); @@ -2926,7 +2960,7 @@ final class CodeGenerator extends NodeOperatorVisitor { method.label(exitLabel); method.store(symbol); - return null; + return false; } /** @@ -2955,7 +2989,7 @@ final class CodeGenerator extends NodeOperatorVisitor { if (scopeCalls.containsKey(scopeCall)) { return scopeCalls.get(scopeCall); } - scopeCall.setClassAndName(getCurrentCompileUnit(), getCurrentFunctionNode().uniqueName("scopeCall")); + scopeCall.setClassAndName(unit, getLexicalContext().getCurrentFunction().uniqueName("scopeCall")); scopeCalls.put(scopeCall, scopeCall); return scopeCall; } @@ -2974,7 +3008,7 @@ final class CodeGenerator extends NodeOperatorVisitor { if (scopeCalls.containsKey(scopeCall)) { return scopeCalls.get(scopeCall); } - scopeCall.setClassAndName(getCurrentCompileUnit(), getCurrentFunctionNode().uniqueName("scopeCall")); + scopeCall.setClassAndName(unit, getLexicalContext().getCurrentFunction().uniqueName("scopeCall")); scopeCalls.put(scopeCall, scopeCall); return scopeCall; } @@ -3037,9 +3071,6 @@ final class CodeGenerator extends NodeOperatorVisitor { /** The target node to store to, e.g. x */ private final Node target; - /** Should the result always be discarded, no matter what? */ - private final boolean alwaysDiscard; - /** How deep on the stack do the arguments go if this generates an indy call */ private int depth; @@ -3055,7 +3086,6 @@ final class CodeGenerator extends NodeOperatorVisitor { protected Store(final T assignNode, final Node target) { this.assignNode = assignNode; this.target = target; - this.alwaysDiscard = assignNode == target; } /** @@ -3077,21 +3107,21 @@ final class CodeGenerator extends NodeOperatorVisitor { private void prologue() { final Symbol targetSymbol = target.getSymbol(); - final Symbol scopeSymbol = getCurrentFunctionNode().getScopeNode().getSymbol(); + final Symbol scopeSymbol = getLexicalContext().getCurrentFunction().compilerConstant(SCOPE); /** * This loads the parts of the target, e.g base and index. they are kept * on the stack throughout the store and used at the end to execute it */ - target.accept(new NodeVisitor(getCurrentCompileUnit(), method) { + target.accept(new NodeVisitor() { @Override - public Node enterIdentNode(final IdentNode node) { + public boolean enterIdentNode(final IdentNode node) { if (targetSymbol.isScope()) { method.load(scopeSymbol); depth++; } - return null; + return false; } private void enterBaseNode() { @@ -3109,13 +3139,13 @@ final class CodeGenerator extends NodeOperatorVisitor { } @Override - public Node enterAccessNode(final AccessNode node) { + public boolean enterAccessNode(final AccessNode node) { enterBaseNode(); - return null; + return false; } @Override - public Node enterIndexNode(final IndexNode node) { + public boolean enterIndexNode(final IndexNode node) { enterBaseNode(); final Node index = node.getIndex(); @@ -3131,14 +3161,14 @@ final class CodeGenerator extends NodeOperatorVisitor { method.dup(1); } - return null; + return false; } }); } private Symbol quickSymbol(final Type type) { - return quickSymbol(type, QUICK_PREFIX.tag()); + return quickSymbol(type, QUICK_PREFIX.symbolName()); } /** @@ -3151,22 +3181,28 @@ final class CodeGenerator extends NodeOperatorVisitor { * @return the quick symbol */ private Symbol quickSymbol(final Type type, final String prefix) { - final String name = getCurrentFunctionNode().uniqueName(prefix); - final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL, null, null); + final String name = getLexicalContext().getCurrentFunction().uniqueName(prefix); + final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL); symbol.setType(type); - symbol.setSlot(getCurrentBlock().getFrame().getSlotCount()); + final int quickSlot = nextFreeSlots[nextFreeSlotsSize - 1]; + nextFreeSlots[nextFreeSlotsSize - 1] = quickSlot + symbol.slotCount(); + symbol.setSlot(quickSlot); return symbol; } // store the result that "lives on" after the op, e.g. "i" in i++ postfix. protected void storeNonDiscard() { - if (assignNode.shouldDiscard() || alwaysDiscard) { - assignNode.setDiscard(false); + if (discard.peek() == assignNode) { + assert assignNode.isAssignment(); + discard.pop(); return; } + //System.err.println("Store with out discard that shouldn't just return " + assignNode); + //new Throwable().printStackTrace(); + final Symbol symbol = assignNode.getSymbol(); if (symbol.hasSlot()) { method.dup().store(symbol); @@ -3191,22 +3227,22 @@ final class CodeGenerator extends NodeOperatorVisitor { */ method.convert(target.getType()); - target.accept(new NodeVisitor(getCurrentCompileUnit(), method) { + target.accept(new NodeVisitor() { @Override - protected Node enterDefault(Node node) { + protected boolean enterDefault(Node node) { throw new AssertionError("Unexpected node " + node + " in store epilogue"); } @Override - public Node enterUnaryNode(final UnaryNode node) { - if(node.tokenType() == TokenType.CONVERT && node.getSymbol() != null) { + public boolean enterUnaryNode(final UnaryNode node) { + if (node.tokenType() == TokenType.CONVERT && node.getSymbol() != null) { method.convert(node.rhs().getType()); } - return node; + return true; } @Override - public Node enterIdentNode(final IdentNode node) { + public boolean enterIdentNode(final IdentNode node) { final Symbol symbol = node.getSymbol(); assert symbol != null; if (symbol.isScope()) { @@ -3218,20 +3254,20 @@ final class CodeGenerator extends NodeOperatorVisitor { } else { method.store(symbol); } - return null; + return false; } @Override - public Node enterAccessNode(final AccessNode node) { + public boolean enterAccessNode(final AccessNode node) { method.dynamicSet(node.getProperty().getType(), node.getProperty().getName(), getCallSiteFlags()); - return null; + return false; } @Override - public Node enterIndexNode(final IndexNode node) { + public boolean enterIndexNode(final IndexNode node) { method.dynamicSetIndex(getCallSiteFlags()); - return null; + return false; } }); @@ -3250,10 +3286,23 @@ final class CodeGenerator extends NodeOperatorVisitor { method.load(quick); } } - } - private void newFunctionObject(final FunctionNode functionNode) { + private void newFunctionObject(final FunctionNode functionNode, final FunctionNode originalFunctionNode) { + final LexicalContext lc = getLexicalContext(); + assert lc.peek() == functionNode; + // We don't emit a ScriptFunction on stack for: + // 1. the outermost compiled function (as there's no code being generated in its outer context that'd need it + // as a callee), and + // 2. for functions that are immediately called upon definition and they don't need a callee, e.g. (function(){})(). + // Such immediately-called functions are invoked using INVOKESTATIC (see enterFunctionNode() of the embedded + // visitor of enterCallNode() for details), and if they don't need a callee, they don't have it on their + // static method's parameter list. + if(lc.getOutermostFunction() == functionNode || + (!functionNode.needsCallee()) && lc.isFunctionDefinedInCurrentCall(originalFunctionNode)) { + return; + } + final boolean isLazy = functionNode.isLazy(); new ObjectCreator(this, new ArrayList<String>(), new ArrayList<Symbol>(), false, false) { @@ -3265,7 +3314,7 @@ final class CodeGenerator extends NodeOperatorVisitor { loadConstant(new RecompilableScriptFunctionData(functionNode, compiler.getCodeInstaller(), Compiler.binaryName(getClassName()), makeMap())); if (isLazy || functionNode.needsParentScope()) { - m.loadScope(); + m.loadCompilerConstant(SCOPE); } else { m.loadNull(); } diff --git a/src/jdk/nashorn/internal/codegen/CompilationPhase.java b/src/jdk/nashorn/internal/codegen/CompilationPhase.java index 8b905f87..884a68bf 100644 --- a/src/jdk/nashorn/internal/codegen/CompilationPhase.java +++ b/src/jdk/nashorn/internal/codegen/CompilationPhase.java @@ -15,6 +15,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; @@ -29,8 +30,8 @@ import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.Timing; /** - * A compilation phase is a step in the processes of turning a JavaScript FunctionNode - * into bytecode. It has an optional return value. + * A compilation phase is a step in the processes of turning a JavaScript + * FunctionNode into bytecode. It has an optional return value. */ enum CompilationPhase { @@ -41,77 +42,87 @@ enum CompilationPhase { */ LAZY_INITIALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED)) { @Override - void transform(final Compiler compiler, final FunctionNode fn) { + FunctionNode transform(final Compiler compiler, final FunctionNode fn0) { /* - * For lazy compilation, we might be given a node previously marked as lazy - * to compile as the outermost function node in the compiler. Unmark it - * so it can be compiled and not cause recursion. Make sure the return type - * is unknown so it can be correctly deduced. Return types are always - * Objects in Lazy nodes as we haven't got a change to generate code for - * them and decude its parameter specialization + * For lazy compilation, we might be given a node previously marked + * as lazy to compile as the outermost function node in the + * compiler. Unmark it so it can be compiled and not cause + * recursion. Make sure the return type is unknown so it can be + * correctly deduced. Return types are always Objects in Lazy nodes + * as we haven't got a change to generate code for them and decude + * its parameter specialization * - * TODO: in the future specializations from a callsite will be passed here - * so we can generate a better non-lazy version of a function from a trampoline + * TODO: in the future specializations from a callsite will be + * passed here so we can generate a better non-lazy version of a + * function from a trampoline */ - //compute the signature from the callsite - todo - now just clone object params + final FunctionNode outermostFunctionNode = compiler.getFunctionNode(); - outermostFunctionNode.setIsLazy(false); - outermostFunctionNode.setReturnType(Type.UNKNOWN); + assert outermostFunctionNode == fn0; final Set<FunctionNode> neverLazy = new HashSet<>(); - final Set<FunctionNode> lazy = new HashSet<>(); + final Set<FunctionNode> lazy = new HashSet<>(); + + FunctionNode newFunctionNode = outermostFunctionNode; - outermostFunctionNode.accept(new NodeVisitor() { - // self references are done with invokestatic and thus cannot have trampolines - never lazy + newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor() { + // self references are done with invokestatic and thus cannot + // have trampolines - never lazy @Override - public Node enterCallNode(final CallNode node) { + public boolean enterCallNode(final CallNode node) { final Node callee = node.getFunction(); if (callee instanceof FunctionNode) { neverLazy.add(((FunctionNode)callee)); - return null; + return false; } - return node; + return true; } + //any function that isn't the outermost one must be marked as lazy @Override - public Node enterFunctionNode(final FunctionNode node) { - if (node == outermostFunctionNode) { - return node; - } + public boolean enterFunctionNode(final FunctionNode node) { assert compiler.isLazy(); lazy.add(node); - - //also needs scope, potentially needs arguments etc etc - - return node; + return true; } }); + //at least one method is non lazy - the outermost one + neverLazy.add(newFunctionNode); + for (final FunctionNode node : neverLazy) { - Compiler.LOG.fine("Marking " + node.getName() + " as non lazy, as it's a self reference"); - node.setIsLazy(false); + Compiler.LOG.fine( + "Marking ", + node.getName(), + " as non lazy, as it's a self reference"); lazy.remove(node); } - outermostFunctionNode.accept(new NodeOperatorVisitor() { - private final LexicalContext lexicalContext = new LexicalContext(); + newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeOperatorVisitor() { @Override - public Node enterFunctionNode(FunctionNode functionNode) { - lexicalContext.push(functionNode); - if(lazy.contains(functionNode)) { - Compiler.LOG.fine("Marking " + functionNode.getName() + " as lazy"); - functionNode.setIsLazy(true); - lexicalContext.getParentFunction(functionNode).setHasLazyChildren(); + public Node leaveFunctionNode(final FunctionNode functionNode) { + final LexicalContext lc = getLexicalContext(); + if (lazy.contains(functionNode)) { + Compiler.LOG.fine( + "Marking ", + functionNode.getName(), + " as lazy"); + final FunctionNode parent = lc.getParentFunction(functionNode); + assert parent != null; + lc.setFlag(parent, FunctionNode.HAS_LAZY_CHILDREN); + lc.setFlag(parent.getBody(), Block.NEEDS_SCOPE); + lc.setFlag(functionNode, FunctionNode.IS_LAZY); + return functionNode; } - return functionNode; - } - @Override - public Node leaveFunctionNode(FunctionNode functionNode) { - lexicalContext.pop(functionNode); - return functionNode; + + return functionNode. + clearFlag(lc, FunctionNode.IS_LAZY). + setReturnType(lc, Type.UNKNOWN); } }); + + return newFunctionNode; } @Override @@ -121,13 +132,13 @@ enum CompilationPhase { }, /* - * Constant folding pass - * Simple constant folding that will make elementary constructs go away + * Constant folding pass Simple constant folding that will make elementary + * constructs go away */ CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED, PARSED)) { @Override - void transform(final Compiler compiler, final FunctionNode fn) { - fn.accept(new FoldConstants()); + FunctionNode transform(final Compiler compiler, final FunctionNode fn) { + return (FunctionNode)fn.accept(new FoldConstants()); } @Override @@ -137,18 +148,16 @@ enum CompilationPhase { }, /* - * Lower (Control flow pass) - * Finalizes the control flow. Clones blocks for finally constructs and - * similar things. Establishes termination criteria for nodes - * Guarantee return instructions to method making sure control flow - * cannot fall off the end. Replacing high level nodes with lower such - * as runtime nodes where applicable. - * + * Lower (Control flow pass) Finalizes the control flow. Clones blocks for + * finally constructs and similar things. Establishes termination criteria + * for nodes Guarantee return instructions to method making sure control + * flow cannot fall off the end. Replacing high level nodes with lower such + * as runtime nodes where applicable. */ LOWERING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED)) { @Override - void transform(final Compiler compiler, final FunctionNode fn) { - fn.accept(new Lower()); + FunctionNode transform(final Compiler compiler, final FunctionNode fn) { + return (FunctionNode)fn.accept(new Lower()); } @Override @@ -158,13 +167,27 @@ enum CompilationPhase { }, /* - * Attribution - * Assign symbols and types to all nodes. + * Attribution Assign symbols and types to all nodes. */ ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) { @Override - void transform(final Compiler compiler, final FunctionNode fn) { - fn.accept(new Attr()); + FunctionNode transform(final Compiler compiler, final FunctionNode fn) { + return (FunctionNode)initReturnTypes(fn).accept(new Attr()); + } + + /** + * Pessimistically set all lazy functions' return types to Object + * @param functionNode node where to start iterating + */ + private FunctionNode initReturnTypes(final FunctionNode functionNode) { + return (FunctionNode)functionNode.accept(new NodeVisitor() { + @Override + public Node leaveFunctionNode(final FunctionNode node) { + return node.isLazy() ? + node.setReturnType(getLexicalContext(), Type.OBJECT) : + node.setReturnType(getLexicalContext(), Type.UNKNOWN); + } + }); } @Override @@ -174,25 +197,35 @@ enum CompilationPhase { }, /* - * Splitter - * Split the AST into several compile units based on a size heuristic - * Splitter needs attributed AST for weight calculations (e.g. is - * a + b a ScriptRuntime.ADD with call overhead or a dadd with much - * less). Split IR can lead to scope information being changed. + * Splitter Split the AST into several compile units based on a size + * heuristic Splitter needs attributed AST for weight calculations (e.g. is + * a + b a ScriptRuntime.ADD with call overhead or a dadd with much less). + * Split IR can lead to scope information being changed. */ SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) { @Override - void transform(final Compiler compiler, final FunctionNode fn) { + FunctionNode transform(final Compiler compiler, final FunctionNode fn) { final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName()); - new Splitter(compiler, fn, outermostCompileUnit).split(); + final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn); - assert fn.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + fn.getCompileUnit() + ") != " + outermostCompileUnit; + assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit; - if (fn.isStrictMode()) { + if (newFunctionNode.isStrict()) { assert compiler.getStrictMode(); compiler.setStrictMode(true); } + + /* + newFunctionNode.accept(new NodeVisitor() { + @Override + public boolean enterFunctionNode(final FunctionNode functionNode) { + assert functionNode.getCompileUnit() != null : functionNode.getName() + " " + Debug.id(functionNode) + " has no compile unit"; + return true; + } + });*/ + + return newFunctionNode; } @Override @@ -204,30 +237,32 @@ enum CompilationPhase { /* * FinalizeTypes * - * This pass finalizes the types for nodes. If Attr created wider types than - * known during the first pass, convert nodes are inserted or access nodes - * are specialized where scope accesses. + * This pass finalizes the types for nodes. If Attr created wider types than + * known during the first pass, convert nodes are inserted or access nodes + * are specialized where scope accesses. * - * Runtime nodes may be removed and primitivized or reintroduced depending - * on information that was established in Attr. + * Runtime nodes may be removed and primitivized or reintroduced depending + * on information that was established in Attr. * * Contract: all variables must have slot assignments and scope assignments * before type finalization. */ TYPE_FINALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT)) { @Override - void transform(final Compiler compiler, final FunctionNode fn) { + FunctionNode transform(final Compiler compiler, final FunctionNode fn) { final ScriptEnvironment env = compiler.getEnv(); - fn.accept(new FinalizeTypes()); + final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new FinalizeTypes()); if (env._print_lower_ast) { - env.getErr().println(new ASTWriter(fn)); + env.getErr().println(new ASTWriter(newFunctionNode)); } if (env._print_lower_parse) { - env.getErr().println(new PrintVisitor(fn)); - } + env.getErr().println(new PrintVisitor(newFunctionNode)); + } + + return newFunctionNode; } @Override @@ -239,31 +274,21 @@ enum CompilationPhase { /* * Bytecode generation: * - * Generate the byte code class(es) resulting from the compiled FunctionNode + * Generate the byte code class(es) resulting from the compiled FunctionNode */ BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED)) { @Override - void transform(final Compiler compiler, final FunctionNode fn) { + FunctionNode transform(final Compiler compiler, final FunctionNode fn) { final ScriptEnvironment env = compiler.getEnv(); + FunctionNode newFunctionNode = fn; try { final CodeGenerator codegen = new CodeGenerator(compiler); - fn.accept(codegen); + newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen); codegen.generateScopeCalls(); - fn.accept(new NodeOperatorVisitor() { - @Override - public Node enterFunctionNode(FunctionNode functionNode) { - if(functionNode.isLazy()) { - functionNode.resetResolved(); - return null; - } - return fn; - } - }); - } catch (final VerifyError e) { if (env._verify_code || env._print_code) { - env.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage()); + env.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage()); if (env._dump_on_error) { e.printStackTrace(env.getErr()); } @@ -283,25 +308,25 @@ enum CompilationPhase { compiler.addClass(className, bytecode); - //should could be printed to stderr for generate class? + // should could be printed to stderr for generate class? if (env._print_code) { final StringBuilder sb = new StringBuilder(); - sb.append("class: " + className). - append('\n'). - append(ClassEmitter.disassemble(bytecode)). - append("====="); + sb.append("class: " + className).append('\n') + .append(ClassEmitter.disassemble(bytecode)) + .append("====="); env.getErr().println(sb); } - //should we verify the generated code? + // should we verify the generated code? if (env._verify_code) { compiler.getCodeInstaller().verify(bytecode); } - //should code be dumped to disk - only valid in compile_only mode? + // should code be dumped to disk - only valid in compile_only + // mode? if (env._dest_dir != null && env._compile_only) { final String fileName = className.replace('.', File.separatorChar) + ".class"; - final int index = fileName.lastIndexOf(File.separatorChar); + final int index = fileName.lastIndexOf(File.separatorChar); if (index != -1) { final File dir = new File(fileName.substring(0, index)); @@ -314,11 +339,18 @@ enum CompilationPhase { fos.write(bytecode); } } catch (final IOException e) { - Compiler.LOG.warning("Skipping class dump for " + className + ": " + ECMAErrors.getMessage("io.error.cant.write", dir.toString())); + Compiler.LOG.warning("Skipping class dump for ", + className, + ": ", + ECMAErrors.getMessage( + "io.error.cant.write", + dir.toString())); } } } } + + return newFunctionNode; } @Override @@ -340,26 +372,28 @@ enum CompilationPhase { return functionNode.hasState(pre); } - protected void begin(final FunctionNode functionNode) { + protected FunctionNode begin(final FunctionNode functionNode) { if (pre != null) { - //check that everything in pre is present + // check that everything in pre is present for (final CompilationState state : pre) { assert functionNode.hasState(state); } - //check that nothing else is present + // check that nothing else is present for (final CompilationState state : CompilationState.values()) { assert !(functionNode.hasState(state) && !pre.contains(state)); } } startTime = System.currentTimeMillis(); + return functionNode; } - protected void end(final FunctionNode functionNode) { + protected FunctionNode end(final FunctionNode functionNode) { endTime = System.currentTimeMillis(); Timing.accumulateTime(toString(), endTime - startTime); isFinished = true; + return functionNode; } boolean isFinished() { @@ -374,15 +408,13 @@ enum CompilationPhase { return endTime; } - abstract void transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException; + abstract FunctionNode transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException; - final void apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException { + final FunctionNode apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException { if (!isApplicable(functionNode)) { - throw new CompilationException("compile phase not applicable: " + this); + throw new CompilationException("compile phase not applicable: " + this + " to " + functionNode.getName() + " state=" + functionNode.getState()); } - begin(functionNode); - transform(compiler, functionNode); - end(functionNode); + return end(transform(compiler, begin(functionNode))); } } diff --git a/src/jdk/nashorn/internal/codegen/Compiler.java b/src/jdk/nashorn/internal/codegen/Compiler.java index 397f39a5..b1e47d02 100644 --- a/src/jdk/nashorn/internal/codegen/Compiler.java +++ b/src/jdk/nashorn/internal/codegen/Compiler.java @@ -25,12 +25,16 @@ package jdk.nashorn.internal.codegen; +import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; import static jdk.nashorn.internal.codegen.CompilerConstants.DEFAULT_SCRIPT_NAME; import static jdk.nashorn.internal.codegen.CompilerConstants.LAZY; +import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; +import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; import java.io.File; import java.lang.reflect.Field; @@ -46,13 +50,12 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; + import jdk.internal.dynalink.support.NameCodec; import jdk.nashorn.internal.codegen.ClassEmitter.Flag; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; -import jdk.nashorn.internal.ir.Node; -import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.CodeInstaller; import jdk.nashorn.internal.runtime.DebugLogger; import jdk.nashorn.internal.runtime.ScriptEnvironment; @@ -80,8 +83,6 @@ public final class Compiler { private final ConstantData constantData; - private final FunctionNode functionNode; - private final CompilationSequence sequence; private final ScriptEnvironment env; @@ -90,6 +91,8 @@ public final class Compiler { private boolean strict; + private FunctionNode functionNode; + private CodeInstaller<ScriptEnvironment> installer; /** logger for compiler, trampolines, splits and related code generation events @@ -103,8 +106,12 @@ public final class Compiler { * during a compile. */ private static String[] RESERVED_NAMES = { - SCOPE.tag(), - THIS.tag() + SCOPE.symbolName(), + THIS.symbolName(), + RETURN.symbolName(), + CALLEE.symbolName(), + VARARGS.symbolName(), + ARGUMENTS.symbolName() }; /** @@ -186,7 +193,7 @@ public final class Compiler { private static String lazyTag(final FunctionNode functionNode) { if (functionNode.isLazy()) { - return '$' + LAZY.tag() + '$' + functionNode.getName(); + return '$' + LAZY.symbolName() + '$' + functionNode.getName(); } return ""; } @@ -205,13 +212,13 @@ public final class Compiler { this.functionNode = functionNode; this.sequence = sequence; this.installer = installer; - this.strict = strict || functionNode.isStrictMode(); + this.strict = strict || functionNode.isStrict(); this.constantData = new ConstantData(); this.compileUnits = new HashSet<>(); this.bytecode = new HashMap<>(); final StringBuilder sb = new StringBuilder(); - sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.tag() + lazyTag(functionNode))). + sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.symbolName() + lazyTag(functionNode))). append('$'). append(safeSourceName(functionNode.getSource())); @@ -253,9 +260,9 @@ public final class Compiler { * Execute the compilation this Compiler was created with * @params param types if known, for specialization * @throws CompilationException if something goes wrong - * @return this compiler, for possible chaining + * @return function node that results from code transforms */ - public Compiler compile() throws CompilationException { + public FunctionNode compile() throws CompilationException { return compile(null); } @@ -263,9 +270,9 @@ public final class Compiler { * Execute the compilation this Compiler was created with * @param paramTypes param types if known, for specialization * @throws CompilationException if something goes wrong - * @return this compiler, for possible chaining + * @return function node that results from code transforms */ - public Compiler compile(final Class<?> paramTypes) throws CompilationException { + public FunctionNode compile(final Class<?> paramTypes) throws CompilationException { for (final String reservedName : RESERVED_NAMES) { functionNode.uniqueName(reservedName); } @@ -276,7 +283,7 @@ public final class Compiler { long time = 0L; for (final CompilationPhase phase : sequence) { - phase.apply(this, functionNode); + this.functionNode = phase.apply(this, functionNode); final long duration = Timing.isEnabled() ? (phase.getEndTime() - phase.getStartTime()) : 0L; time += duration; @@ -295,7 +302,7 @@ public final class Compiler { append(" ms "); } - LOG.fine(sb.toString()); + LOG.fine(sb); } } @@ -311,14 +318,14 @@ public final class Compiler { append(" ms"); } - LOG.info(sb.toString()); + LOG.info(sb); } - return this; + return functionNode; } private Class<?> install(final String className, final byte[] code) { - LOG.fine("Installing class " + className); + LOG.fine("Installing class ", className); final Class<?> clazz = installer.install(Compiler.binaryName(className), code); @@ -330,8 +337,8 @@ public final class Compiler { @Override public Void run() throws Exception { //use reflection to write source and constants table to installed classes - final Field sourceField = clazz.getDeclaredField(SOURCE.tag()); - final Field constantsField = clazz.getDeclaredField(CONSTANTS.tag()); + final Field sourceField = clazz.getDeclaredField(SOURCE.symbolName()); + final Field constantsField = clazz.getDeclaredField(CONSTANTS.symbolName()); sourceField.setAccessible(true); constantsField.setAccessible(true); sourceField.set(null, source); @@ -380,17 +387,6 @@ public final class Compiler { unit.setCode(installedClasses.get(unit.getUnitClassName())); } - functionNode.accept(new NodeVisitor() { - @Override - public Node enterFunctionNode(final FunctionNode node) { - if (node.isLazy()) { - return null; - } - node.setState(CompilationState.INSTALLED); - return node; - } - }); - final StringBuilder sb; if (LOG.isEnabled()) { sb = new StringBuilder(); @@ -416,7 +412,7 @@ public final class Compiler { } if (sb != null) { - LOG.info(sb.toString()); + LOG.info(sb); } return rootClass; @@ -495,7 +491,7 @@ public final class Compiler { private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) { final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight); compileUnits.add(compileUnit); - LOG.fine("Added compile unit " + compileUnit); + LOG.fine("Added compile unit ", compileUnit); return compileUnit; } diff --git a/src/jdk/nashorn/internal/codegen/CompilerConstants.java b/src/jdk/nashorn/internal/codegen/CompilerConstants.java index 1d978d96..dfc9198c 100644 --- a/src/jdk/nashorn/internal/codegen/CompilerConstants.java +++ b/src/jdk/nashorn/internal/codegen/CompilerConstants.java @@ -52,9 +52,6 @@ public enum CompilerConstants { /** lazy prefix for classes of jitted methods */ LAZY("Lazy"), - /** leaf tag used for functions that require no scope */ - LEAF("__leaf__"), - /** constructor name */ INIT("<init>"), @@ -90,55 +87,55 @@ public enum CompilerConstants { THIS("this"), /** this debugger symbol */ - THIS_DEBUGGER("__this__"), + THIS_DEBUGGER(":this"), /** scope name, type and slot */ - SCOPE("__scope__", ScriptObject.class, 2), + SCOPE(":scope", ScriptObject.class, 2), /** the return value variable name were intermediate results are stored for scripts */ - SCRIPT_RETURN("__return__"), + RETURN(":return"), /** the callee value variable when necessary */ - CALLEE("__callee__", ScriptFunction.class), + CALLEE(":callee", ScriptFunction.class), /** the varargs variable when necessary */ - VARARGS("__varargs__"), + VARARGS(":varargs"), /** the arguments vector when necessary and the slot */ ARGUMENTS("arguments", Object.class, 2), /** prefix for iterators for for (x in ...) */ - ITERATOR_PREFIX("$iter"), + ITERATOR_PREFIX(":iter"), /** prefix for tag variable used for switch evaluation */ - SWITCH_TAG_PREFIX("$tag"), + SWITCH_TAG_PREFIX(":tag"), /** prefix for all exceptions */ - EXCEPTION_PREFIX("$exception"), + EXCEPTION_PREFIX(":exception"), /** prefix for quick slots generated in Store */ - QUICK_PREFIX("$quick"), + QUICK_PREFIX(":quick"), /** prefix for temporary variables */ - TEMP_PREFIX("$temp"), + TEMP_PREFIX(":temp"), /** prefix for literals */ - LITERAL_PREFIX("$lit"), - - /** prefix for map */ - MAP("$map", 1), + LITERAL_PREFIX(":lit"), /** prefix for regexps */ - REGEX_PREFIX("$regex"), + REGEX_PREFIX(":regex"), /** "this" used in non-static Java methods; always in slot 0 */ - JAVA_THIS("this", 0), + JAVA_THIS(null, 0), + + /** Map parameter in scope object constructors; always in slot 1 */ + INIT_MAP(null, 1), - /** init scope */ - INIT_SCOPE("$scope", 2), + /** Parent scope parameter in scope object constructors; always in slot 2 */ + INIT_SCOPE(null, 2), - /** init arguments */ - INIT_ARGUMENTS("$arguments", 3), + /** Arguments parameter in scope object constructors; in slot 3 when present */ + INIT_ARGUMENTS(null, 3), /** prefix for all ScriptObject subclasses with fields, @see ObjectGenerator */ JS_OBJECT_PREFIX("JO"), @@ -167,30 +164,30 @@ public enum CompilerConstants { /** get array suffix */ GET_ARRAY_SUFFIX("$array"); - private final String tag; + private final String symbolName; private final Class<?> type; private final int slot; private CompilerConstants() { - this.tag = name(); + this.symbolName = name(); this.type = null; this.slot = -1; } - private CompilerConstants(final String tag) { - this(tag, -1); + private CompilerConstants(final String symbolName) { + this(symbolName, -1); } - private CompilerConstants(final String tag, final int slot) { - this(tag, null, slot); + private CompilerConstants(final String symbolName, final int slot) { + this(symbolName, null, slot); } - private CompilerConstants(final String tag, final Class<?> type) { - this(tag, type, -1); + private CompilerConstants(final String symbolName, final Class<?> type) { + this(symbolName, type, -1); } - private CompilerConstants(final String tag, final Class<?> type, final int slot) { - this.tag = tag; + private CompilerConstants(final String symbolName, final Class<?> type, final int slot) { + this.symbolName = symbolName; this.type = type; this.slot = slot; } @@ -202,8 +199,8 @@ public enum CompilerConstants { * * @return the tag */ - public final String tag() { - return tag; + public final String symbolName() { + return symbolName; } /** @@ -277,7 +274,7 @@ public enum CompilerConstants { * @return Call representing void constructor for type */ public static Call constructorNoLookup(final Class<?> clazz) { - return specialCallNoLookup(clazz, INIT.tag(), void.class); + return specialCallNoLookup(clazz, INIT.symbolName(), void.class); } /** @@ -290,7 +287,7 @@ public enum CompilerConstants { * @return Call representing constructor for type */ public static Call constructorNoLookup(final String className, final Class<?>... ptypes) { - return specialCallNoLookup(className, INIT.tag(), methodDescriptor(void.class, ptypes)); + return specialCallNoLookup(className, INIT.symbolName(), methodDescriptor(void.class, ptypes)); } /** @@ -303,7 +300,7 @@ public enum CompilerConstants { * @return Call representing constructor for type */ public static Call constructorNoLookup(final Class<?> clazz, final Class<?>... ptypes) { - return specialCallNoLookup(clazz, INIT.tag(), void.class, ptypes); + return specialCallNoLookup(clazz, INIT.symbolName(), void.class, ptypes); } /** diff --git a/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java b/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java index 83e75cc7..57b8b384 100644 --- a/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java +++ b/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java @@ -26,6 +26,7 @@ package jdk.nashorn.internal.codegen; import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; import static jdk.nashorn.internal.codegen.types.Type.OBJECT; @@ -86,7 +87,7 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator { * @param method the method emitter to use */ protected void loadScope(final MethodEmitter method) { - method.loadScope(); + method.loadCompilerConstant(SCOPE); } /** @@ -105,7 +106,7 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator { loadScope(method); if (hasArguments()) { - method.loadArguments(); + method.loadCompilerConstant(ARGUMENTS); method.invoke(constructorNoLookup(getClassName(), PropertyMap.class, ScriptObject.class, ARGUMENTS.type())); } else { method.invoke(constructorNoLookup(getClassName(), PropertyMap.class, ScriptObject.class)); diff --git a/src/jdk/nashorn/internal/codegen/FinalizeTypes.java b/src/jdk/nashorn/internal/codegen/FinalizeTypes.java index cd185240..5f6a63dc 100644 --- a/src/jdk/nashorn/internal/codegen/FinalizeTypes.java +++ b/src/jdk/nashorn/internal/codegen/FinalizeTypes.java @@ -25,7 +25,12 @@ package jdk.nashorn.internal.codegen; +import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; +import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; + +import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.AccessNode; @@ -33,10 +38,8 @@ import jdk.nashorn.internal.ir.Assignment; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.CallNode; -import jdk.nashorn.internal.ir.CallNode.EvalArgs; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; -import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; @@ -85,18 +88,11 @@ final class FinalizeTypes extends NodeOperatorVisitor { private static final DebugLogger LOG = new DebugLogger("finalize"); - private final LexicalContext lexicalContext = new LexicalContext(); - FinalizeTypes() { } @Override public Node leaveCallNode(final CallNode callNode) { - final EvalArgs evalArgs = callNode.getEvalArgs(); - if (evalArgs != null) { - evalArgs.setCode(evalArgs.getCode().accept(this)); - } - // AccessSpecializer - call return type may change the access for this location final Node function = callNode.getFunction(); if (function instanceof FunctionNode) { @@ -133,8 +129,7 @@ final class FinalizeTypes extends NodeOperatorVisitor { @Override public Node leaveNEW(final UnaryNode unaryNode) { assert unaryNode.getSymbol() != null && unaryNode.getSymbol().getSymbolType().isObject(); - ((CallNode)unaryNode.rhs()).setIsNew(); - return unaryNode; + return unaryNode.setRHS(((CallNode)unaryNode.rhs()).setIsNew()); } @Override @@ -254,7 +249,7 @@ final class FinalizeTypes extends NodeOperatorVisitor { @Override public Node leaveCOMMALEFT(final BinaryNode binaryNode) { assert binaryNode.getSymbol() != null; - final BinaryNode newBinaryNode = (BinaryNode)binaryNode.setRHS(discard(binaryNode.rhs())); + final BinaryNode newBinaryNode = binaryNode.setRHS(discard(binaryNode.rhs())); // AccessSpecializer - the type of lhs, which is the remaining value of this node may have changed // in that case, update the node type as well propagateType(newBinaryNode, newBinaryNode.lhs().getType()); @@ -354,41 +349,30 @@ final class FinalizeTypes extends NodeOperatorVisitor { } @Override - public Node enterBlock(final Block block) { - lexicalContext.push(block); + public boolean enterBlock(final Block block) { updateSymbols(block); - return block; + return true; } + /* @Override - public Node leaveBlock(Block block) { - lexicalContext.pop(block); - return super.leaveBlock(block); - } + public Node leaveBlock(final Block block) { + final LexicalContext lc = getLexicalContext(); + return block;//.setFlag(lc, lc.getFlags(block)); + }*/ @Override public Node leaveCatchNode(final CatchNode catchNode) { final Node exceptionCondition = catchNode.getExceptionCondition(); if (exceptionCondition != null) { - catchNode.setExceptionCondition(convert(exceptionCondition, Type.BOOLEAN)); + return catchNode.setExceptionCondition(convert(exceptionCondition, Type.BOOLEAN)); } return catchNode; } @Override - public Node enterDoWhileNode(final DoWhileNode doWhileNode) { - return enterWhileNode(doWhileNode); - } - - @Override - public Node leaveDoWhileNode(final DoWhileNode doWhileNode) { - return leaveWhileNode(doWhileNode); - } - - @Override public Node leaveExecuteNode(final ExecuteNode executeNode) { - executeNode.setExpression(discard(executeNode.getExpression())); - return executeNode; + return executeNode.setExpression(discard(executeNode.getExpression())); } @Override @@ -397,69 +381,54 @@ final class FinalizeTypes extends NodeOperatorVisitor { final Node test = forNode.getTest(); final Node modify = forNode.getModify(); - if (forNode.isForIn()) { - forNode.setModify(convert(forNode.getModify(), Type.OBJECT)); // NASHORN-400 - return forNode; - } + final LexicalContext lc = getLexicalContext(); - if (init != null) { - forNode.setInit(discard(init)); - } - - if (test != null) { - forNode.setTest(convert(test, Type.BOOLEAN)); - } else { - assert forNode.hasGoto() : "forNode " + forNode + " needs goto and is missing it in " + getCurrentFunctionNode(); - } - - if (modify != null) { - forNode.setModify(discard(modify)); + if (forNode.isForIn()) { + return forNode.setModify(lc, convert(forNode.getModify(), Type.OBJECT)); // NASHORN-400 } + assert test != null || forNode.hasGoto() : "forNode " + forNode + " needs goto and is missing it in " + getLexicalContext().getCurrentFunction(); - return forNode; + return forNode. + setInit(lc, init == null ? null : discard(init)). + setTest(lc, test == null ? null : convert(test, Type.BOOLEAN)). + setModify(lc, modify == null ? null : discard(modify)); } @Override - public Node enterFunctionNode(final FunctionNode functionNode) { + public boolean enterFunctionNode(final FunctionNode functionNode) { if (functionNode.isLazy()) { - return null; + return false; } - lexicalContext.push(functionNode); // If the function doesn't need a callee, we ensure its __callee__ symbol doesn't get a slot. We can't do // this earlier, as access to scoped variables, self symbol, etc. in previous phases can all trigger the // need for the callee. if (!functionNode.needsCallee()) { - functionNode.getCalleeNode().getSymbol().setNeedsSlot(false); + functionNode.compilerConstant(CALLEE).setNeedsSlot(false); } // Similar reasoning applies to __scope__ symbol: if the function doesn't need either parent scope or its // own scope, we ensure it doesn't get a slot, but we can't determine whether it needs a scope earlier than // this phase. - if (!(functionNode.needsScope() || functionNode.needsParentScope())) { - functionNode.getScopeNode().getSymbol().setNeedsSlot(false); + if (!(functionNode.getBody().needsScope() || functionNode.needsParentScope())) { + functionNode.compilerConstant(SCOPE).setNeedsSlot(false); } - updateSymbols(functionNode); - functionNode.setState(CompilationState.FINALIZED); - - return functionNode; + return true; } @Override - public Node leaveFunctionNode(FunctionNode functionNode) { - lexicalContext.pop(functionNode); - return super.leaveFunctionNode(functionNode); + public Node leaveFunctionNode(final FunctionNode functionNode) { + return functionNode.setState(getLexicalContext(), CompilationState.FINALIZED); } @Override public Node leaveIfNode(final IfNode ifNode) { - ifNode.setTest(convert(ifNode.getTest(), Type.BOOLEAN)); - return ifNode; + return ifNode.setTest(convert(ifNode.getTest(), Type.BOOLEAN)); } @SuppressWarnings("rawtypes") @Override - public Node enterLiteralNode(final LiteralNode literalNode) { + public boolean enterLiteralNode(final LiteralNode literalNode) { if (literalNode instanceof ArrayLiteralNode) { final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode)literalNode; final Node[] array = arrayLiteralNode.getValue(); @@ -473,14 +442,14 @@ final class FinalizeTypes extends NodeOperatorVisitor { } } - return null; + return false; } @Override public Node leaveReturnNode(final ReturnNode returnNode) { final Node expr = returnNode.getExpression(); if (expr != null) { - returnNode.setExpression(convert(expr, getCurrentFunctionNode().getReturnType())); + return returnNode.setExpression(convert(expr, getLexicalContext().getCurrentFunction().getReturnType())); } return returnNode; } @@ -496,21 +465,24 @@ final class FinalizeTypes extends NodeOperatorVisitor { @Override public Node leaveSwitchNode(final SwitchNode switchNode) { + final boolean allInteger = switchNode.getTag().getSymbolType().isInteger(); + + if (allInteger) { + return switchNode; + } + final Node expression = switchNode.getExpression(); final List<CaseNode> cases = switchNode.getCases(); - final boolean allInteger = switchNode.getTag().getSymbolType().isInteger(); - - if (!allInteger) { - switchNode.setExpression(convert(expression, Type.OBJECT)); - for (final CaseNode caseNode : cases) { - final Node test = caseNode.getTest(); - if (test != null) { - caseNode.setTest(convert(test, Type.OBJECT)); - } - } + final List<CaseNode> newCases = new ArrayList<>(); + + for (final CaseNode caseNode : cases) { + final Node test = caseNode.getTest(); + newCases.add(test != null ? caseNode.setTest(convert(test, Type.OBJECT)) : caseNode); } - return switchNode; + return switchNode. + setExpression(getLexicalContext(), convert(expression, Type.OBJECT)). + setCases(getLexicalContext(), newCases); } @Override @@ -520,8 +492,7 @@ final class FinalizeTypes extends NodeOperatorVisitor { @Override public Node leaveThrowNode(final ThrowNode throwNode) { - throwNode.setExpression(convert(throwNode.getExpression(), Type.OBJECT)); - return throwNode; + return throwNode.setExpression(convert(throwNode.getExpression(), Type.OBJECT)); } @Override @@ -544,23 +515,24 @@ final class FinalizeTypes extends NodeOperatorVisitor { public Node leaveWhileNode(final WhileNode whileNode) { final Node test = whileNode.getTest(); if (test != null) { - whileNode.setTest(convert(test, Type.BOOLEAN)); + return whileNode.setTest(getLexicalContext(), convert(test, Type.BOOLEAN)); } return whileNode; } @Override public Node leaveWithNode(final WithNode withNode) { - withNode.setExpression(convert(withNode.getExpression(), Type.OBJECT)); - return withNode; + return withNode.setExpression(getLexicalContext(), convert(withNode.getExpression(), Type.OBJECT)); } private static void updateSymbolsLog(final FunctionNode functionNode, final Symbol symbol, final boolean loseSlot) { - if (!symbol.isScope()) { - LOG.finest("updateSymbols: " + symbol + " => scope, because all vars in " + functionNode.getName() + " are in scope"); - } - if (loseSlot && symbol.hasSlot()) { - LOG.finest("updateSymbols: " + symbol + " => no slot, because all vars in " + functionNode.getName() + " are in scope"); + if (LOG.isEnabled()) { + if (!symbol.isScope()) { + LOG.finest("updateSymbols: ", symbol, " => scope, because all vars in ", functionNode.getName(), " are in scope"); + } + if (loseSlot && symbol.hasSlot()) { + LOG.finest("updateSymbols: ", symbol, " => no slot, because all vars in ", functionNode.getName(), " are in scope"); + } } } @@ -574,29 +546,28 @@ final class FinalizeTypes extends NodeOperatorVisitor { return; // nothing to do } - final FunctionNode functionNode = lexicalContext.getFunction(block); - assert !(block instanceof FunctionNode) || functionNode == block; + final LexicalContext lc = getLexicalContext(); + final FunctionNode functionNode = lc.getFunction(block); + final boolean allVarsInScope = functionNode.allVarsInScope(); + final boolean isVarArg = functionNode.isVarArg(); - final List<Symbol> symbols = block.getFrame().getSymbols(); - final boolean allVarsInScope = functionNode.allVarsInScope(); - final boolean isVarArg = functionNode.isVarArg(); - - for (final Symbol symbol : symbols) { - if (symbol.isInternal() || symbol.isThis()) { + for (final Iterator<Symbol> iter = block.symbolIterator(); iter.hasNext(); ) { + final Symbol symbol = iter.next(); + if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) { continue; } if (symbol.isVar()) { if (allVarsInScope || symbol.isScope()) { updateSymbolsLog(functionNode, symbol, true); - symbol.setIsScope(); + Symbol.setSymbolIsScope(lc, symbol); symbol.setNeedsSlot(false); } else { assert symbol.hasSlot() : symbol + " should have a slot only, no scope"; } } else if (symbol.isParam() && (allVarsInScope || isVarArg || symbol.isScope())) { updateSymbolsLog(functionNode, symbol, isVarArg); - symbol.setIsScope(); + Symbol.setSymbolIsScope(lc, symbol); symbol.setNeedsSlot(!isVarArg); } } @@ -636,11 +607,7 @@ final class FinalizeTypes extends NodeOperatorVisitor { //fallthru default: if (newRuntimeNode || widest.isObject()) { - final RuntimeNode runtimeNode = new RuntimeNode(binaryNode, request); - if (finalized) { - runtimeNode.setIsFinal(); - } - return runtimeNode; + return new RuntimeNode(binaryNode, request).setIsFinal(finalized); } break; } @@ -667,7 +634,8 @@ final class FinalizeTypes extends NodeOperatorVisitor { } private Node leaveBinary(final BinaryNode binaryNode, final Type lhsType, final Type rhsType) { - return binaryNode.setLHS(convert(binaryNode.lhs(), lhsType)).setRHS(convert(binaryNode.rhs(), rhsType)); + Node b = binaryNode.setLHS(convert(binaryNode.lhs(), lhsType)).setRHS(convert(binaryNode.rhs(), rhsType)); + return b; } /** @@ -683,28 +651,28 @@ final class FinalizeTypes extends NodeOperatorVisitor { node.accept(new NodeVisitor() { private void setCanBePrimitive(final Symbol symbol) { - LOG.info("*** can be primitive symbol " + symbol + " " + Debug.id(symbol)); + LOG.info("*** can be primitive symbol ", symbol, " ", Debug.id(symbol)); symbol.setCanBePrimitive(to); } @Override - public Node enterIdentNode(final IdentNode identNode) { + public boolean enterIdentNode(final IdentNode identNode) { if (!exclude.contains(identNode)) { setCanBePrimitive(identNode.getSymbol()); } - return null; + return false; } @Override - public Node enterAccessNode(final AccessNode accessNode) { + public boolean enterAccessNode(final AccessNode accessNode) { setCanBePrimitive(accessNode.getProperty().getSymbol()); - return null; + return false; } @Override - public Node enterIndexNode(final IndexNode indexNode) { + public boolean enterIndexNode(final IndexNode indexNode) { exclude.add(indexNode.getBase()); //prevent array base node to be flagged as primitive, but k in a[k++] is fine - return indexNode; + return true; } }); } @@ -785,12 +753,12 @@ final class FinalizeTypes extends NodeOperatorVisitor { private static <T extends Node> T setTypeOverride(final T node, final Type to) { final Type from = node.getType(); if (!node.getType().equals(to)) { - LOG.info("Changing call override type for '" + node + "' from " + node.getType() + " to " + to); + LOG.info("Changing call override type for '", node, "' from ", node.getType(), " to ", to); if (!to.isObject() && from.isObject()) { setCanBePrimitive(node, to); } } - LOG.info("Type override for lhs in '" + node + "' => " + to); + LOG.info("Type override for lhs in '", node, "' => ", to); return ((TypeOverride<T>)node).setType(to); } @@ -814,8 +782,8 @@ final class FinalizeTypes extends NodeOperatorVisitor { private Node convert(final Node node, final Type to) { assert !to.isUnknown() : "unknown type for " + node + " class=" + node.getClass(); assert node != null : "node is null"; - assert node.getSymbol() != null : "node " + node + " has no symbol!"; - assert node.tokenType() != TokenType.CONVERT : "assert convert in convert " + node + " in " + getCurrentFunctionNode(); + assert node.getSymbol() != null : "node " + node + " " + node.getClass() + " has no symbol! " + getLexicalContext().getCurrentFunction() + " " + node.getSource(); + assert node.tokenType() != TokenType.CONVERT : "assert convert in convert " + node + " in " + getLexicalContext().getCurrentFunction(); final Type from = node.getType(); @@ -842,23 +810,23 @@ final class FinalizeTypes extends NodeOperatorVisitor { resultNode = new UnaryNode(node.getSource(), Token.recast(node.getToken(), TokenType.CONVERT), node); } - LOG.info("CONVERT('" + node + "', " + to + ") => '" + resultNode + "'"); + LOG.info("CONVERT('", node, "', ", to, ") => '", resultNode, "'"); + final LexicalContext lc = getLexicalContext(); //This is the only place in this file that can create new temporaries //FinalizeTypes may not introduce ANY node that is not a conversion. - getCurrentFunctionNode().newTemporary(getCurrentBlock().getFrame(), to, resultNode); - resultNode.copyTerminalFlags(node); + lc.getCurrentFunction().ensureSymbol(lc.getCurrentBlock(), to, resultNode); + + assert !node.isTerminal(); return resultNode; } private static Node discard(final Node node) { - node.setDiscard(true); - if (node.getSymbol() != null) { final Node discard = new UnaryNode(node.getSource(), Token.recast(node.getToken(), TokenType.DISCARD), node); //discard never has a symbol in the discard node - then it would be a nop - discard.copyTerminalFlags(node); + assert !node.isTerminal(); return discard; } @@ -883,7 +851,7 @@ final class FinalizeTypes extends NodeOperatorVisitor { final Symbol symbol = node.getSymbol(); if (symbol.isTemp()) { symbol.setTypeOverride(to); - LOG.info("Type override for temporary in '" + node + "' => " + to); + LOG.info("Type override for temporary in '", node, "' => ", to); } } diff --git a/src/jdk/nashorn/internal/codegen/FoldConstants.java b/src/jdk/nashorn/internal/codegen/FoldConstants.java index fbc62644..03accb4b 100644 --- a/src/jdk/nashorn/internal/codegen/FoldConstants.java +++ b/src/jdk/nashorn/internal/codegen/FoldConstants.java @@ -57,7 +57,7 @@ final class FoldConstants extends NodeVisitor { public Node leaveUnaryNode(final UnaryNode unaryNode) { final LiteralNode<?> literalNode = new UnaryNodeConstantEvaluator(unaryNode).eval(); if (literalNode != null) { - LOG.info("Unary constant folded " + unaryNode + " to " + literalNode); + LOG.info("Unary constant folded ", unaryNode, " to ", literalNode); return literalNode; } return unaryNode; @@ -67,24 +67,20 @@ final class FoldConstants extends NodeVisitor { public Node leaveBinaryNode(final BinaryNode binaryNode) { final LiteralNode<?> literalNode = new BinaryNodeConstantEvaluator(binaryNode).eval(); if (literalNode != null) { - LOG.info("Binary constant folded " + binaryNode + " to " + literalNode); + LOG.info("Binary constant folded ", binaryNode, " to ", literalNode); return literalNode; } return binaryNode; } @Override - public Node enterFunctionNode(final FunctionNode functionNode) { - if (functionNode.isLazy()) { - return null; - } - return functionNode; + public boolean enterFunctionNode(final FunctionNode functionNode) { + return !functionNode.isLazy(); } @Override public Node leaveFunctionNode(final FunctionNode functionNode) { - functionNode.setState(CompilationState.CONSTANT_FOLDED); - return functionNode; + return functionNode.setState(getLexicalContext(), CompilationState.CONSTANT_FOLDED); } @Override @@ -251,7 +247,7 @@ final class FoldConstants extends NodeVisitor { value = lhs.getNumber() - rhs.getNumber(); break; case SHR: - return LiteralNode.newInstance(source, token, finish, (lhs.getInt32() >>> rhs.getInt32()) & 0xffff_ffffL); + return LiteralNode.newInstance(source, token, finish, (lhs.getInt32() >>> rhs.getInt32()) & JSType.MAX_UINT); case SAR: return LiteralNode.newInstance(source, token, finish, lhs.getInt32() >> rhs.getInt32()); case SHL: diff --git a/src/jdk/nashorn/internal/codegen/Frame.java b/src/jdk/nashorn/internal/codegen/Frame.java deleted file mode 100644 index b72456b2..00000000 --- a/src/jdk/nashorn/internal/codegen/Frame.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - -package jdk.nashorn.internal.codegen; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import jdk.nashorn.internal.ir.Symbol; - -/** - * Tracks the variable area state. - * - */ -public final class Frame { - /** Previous frame. */ - private Frame previous; - - /** Current variables. */ - private final ArrayList<Symbol> symbols; - - /** Number of slots in previous frame. */ - private int baseCount; - - /** Number of slots in this frame. */ - private int count; - - /** - * Constructor. - * - * @param previous frame, the parent variable frame - */ - public Frame(final Frame previous) { - this.previous = previous; - this.symbols = new ArrayList<>(); - this.baseCount = getBaseCount(); - this.count = 0; - } - - /** - * Copy constructor - * @param frame - * @param symbols - */ - private Frame(final Frame frame, final List<Symbol> symbols) { - this.previous = frame.getPrevious() == null ? null : new Frame(frame.getPrevious(), frame.getPrevious().getSymbols()); - this.symbols = new ArrayList<>(frame.getSymbols()); - this.baseCount = frame.getBaseCount(); - this.count = frame.getCount(); - } - - /** - * Copy the frame - * - * @return a new frame with the identical contents - */ - public Frame copy() { - return new Frame(this, getSymbols()); - } - - /** - * Add a new variable to the frame. - * @param symbol Symbol representing variable. - */ - public void addSymbol(final Symbol symbol) { - final int slot = symbol.getSlot(); - if (slot < 0) { - symbols.add(symbol); - count += symbol.slotCount(); - } - } - - /** - * Realign slot numbering prior to code generation. - * @return Number of slots in frame. - */ - public int realign() { - baseCount = getBaseCount(); - count = 0; - - for (final Symbol symbol : symbols) { - if (symbol.hasSlot()) { - symbol.setSlot(baseCount + count); - count += symbol.slotCount(); - } - } - - return count; - } - - /** - * Return the slot count of previous frames. - * @return Number of slots in previous frames. - */ - private int getBaseCount() { - return previous != null ? previous.getSlotCount() : 0; - } - - /** - * Determine the number of slots to top of frame. - * @return Number of slots in total. - */ - public int getSlotCount() { - return baseCount + count; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - Frame f = this; - boolean hasPrev = false; - int pos = 0; - - do { - if (hasPrev) { - sb.append("\n"); - } - - sb.append("#"). - append(pos++). - append(" {baseCount:"). - append(baseCount). - append(", "). - append("count:"). - append(count). - append("} "); - - for (final Symbol var : f.getSymbols()) { - sb.append('['). - append(var.toString()). - append(' '). - append(var.hashCode()). - append("] "); - } - - f = f.getPrevious(); - hasPrev = true; - } while (f != null); - - return sb.toString(); - } - - /** - * Get variable count for this frame - * @return variable count - */ - public int getCount() { - return count; - } - - /** - * Get previous frame - * @return previous frame - */ - public Frame getPrevious() { - return previous; - } - - /** - * Set previous frame - * @param previous previous frame - */ - public void setPrevious(final Frame previous) { - this.previous = previous; - } - - /** - * Get symbols in frame - * @return a list of symbols in this frame - */ - public List<Symbol> getSymbols() { - return Collections.unmodifiableList(symbols); - } - } diff --git a/src/jdk/nashorn/internal/codegen/Lower.java b/src/jdk/nashorn/internal/codegen/Lower.java index 715ddc6f..d6209e4f 100644 --- a/src/jdk/nashorn/internal/codegen/Lower.java +++ b/src/jdk/nashorn/internal/codegen/Lower.java @@ -25,29 +25,21 @@ package jdk.nashorn.internal.codegen; -import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; -import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL; -import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; -import static jdk.nashorn.internal.codegen.CompilerConstants.SCRIPT_RETURN; +import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; -import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; -import java.util.Iterator; import java.util.List; import jdk.nashorn.internal.ir.BaseNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.BlockLexicalContext; import jdk.nashorn.internal.ir.BreakNode; import jdk.nashorn.internal.ir.CallNode; -import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; -import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.EmptyNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; @@ -56,10 +48,10 @@ import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; import jdk.nashorn.internal.ir.LabelNode; -import jdk.nashorn.internal.ir.LabeledNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LineNumberNode; import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.LoopNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.SwitchNode; @@ -90,599 +82,415 @@ import jdk.nashorn.internal.runtime.Source; final class Lower extends NodeOperatorVisitor { - /** - * Nesting level stack. Currently just used for loops to avoid the problem - * with terminal bodies that end with throw/return but still do continues to - * outer loops or same loop. - */ - private final Deque<Node> nesting; - private static final DebugLogger LOG = new DebugLogger("lower"); - private Node lastStatement; - - private List<Node> statements; - - private LexicalContext lexicalContext = new LexicalContext(); - /** * Constructor. * * @param compiler the compiler */ Lower() { - this.nesting = new ArrayDeque<>(); - this.statements = new ArrayList<>(); - } + super(new BlockLexicalContext() { - @Override - public Node enterBlock(final Block block) { - final Node savedLastStatement = lastStatement; - final List<Node> savedStatements = statements; - lexicalContext.push(block); - try { - this.statements = new ArrayList<>(); - NodeVisitor visitor = this; - for (final Node statement : block.getStatements()) { - statement.accept(visitor); - /* - * This is slightly unsound, for example if we have a loop with - * a guarded statement like if (x) continue in the body and the - * body ends with TERMINAL, e.g. return; we removed the continue - * before we had the loop stack, as all we cared about was a - * return last in the loop. - * - * @see NASHORN-285 - */ - if (lastStatement != null && lastStatement.isTerminal()) { - copyTerminal(block, lastStatement); - visitor = new DeadCodeVarDeclarationVisitor(); + @Override + public List<Node> popStatements() { + List<Node> newStatements = new ArrayList<>(); + boolean terminated = false; + + final List<Node> statements = super.popStatements(); + for (final Node statement : statements) { + if (!terminated) { + newStatements.add(statement); + if (statement.isTerminal()) { + terminated = true; + } + } else { + if (statement instanceof VarNode) { + newStatements.add(((VarNode)statement).setInit(null)); + } + } } + return newStatements; } - block.setStatements(statements); - - } finally { - this.statements = savedStatements; - this.lastStatement = savedLastStatement; - lexicalContext.pop(block); - } - - return null; + }); } @Override - public Node enterBreakNode(final BreakNode breakNode) { - return enterBreakOrContinue(breakNode); + public boolean enterBlock(final Block block) { + final LexicalContext lc = getLexicalContext(); + if (lc.isFunctionBody() && lc.getCurrentFunction().isProgram() && !lc.getCurrentFunction().hasDeclaredFunctions()) { + new ExecuteNode(block.getSource(), block.getToken(), block.getFinish(), LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)).accept(this); + } + return true; } @Override - public Node enterCallNode(final CallNode callNode) { - final Node function = markerFunction(callNode.getFunction()); - callNode.setFunction(function); - checkEval(callNode); //check if this is an eval call and store the information - return callNode; - } + public Node leaveBlock(final Block block) { + //now we have committed the entire statement list to the block, but we need to truncate + //whatever is after the last terminal. block append won't append past it - @Override - public Node leaveCaseNode(final CaseNode caseNode) { - caseNode.copyTerminalFlags(caseNode.getBody()); - return caseNode; + final BlockLexicalContext lc = (BlockLexicalContext)getLexicalContext(); + + Node last = lc.getLastStatement(); + + if (lc.isFunctionBody()) { + final FunctionNode currentFunction = getLexicalContext().getCurrentFunction(); + final boolean isProgram = currentFunction.isProgram(); + final ReturnNode returnNode = new ReturnNode( + currentFunction.getSource(), + currentFunction.getToken(), + currentFunction.getFinish(), + isProgram ? + compilerConstant(RETURN) : + LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)); + + last = returnNode.accept(this); + } + + if (last != null && last.isTerminal()) { + return block.setIsTerminal(lc, true); + } + + return block; } @Override - public Node leaveCatchNode(final CatchNode catchNode) { - catchNode.copyTerminalFlags(catchNode.getBody()); - addStatement(catchNode); - return catchNode; + public boolean enterBreakNode(final BreakNode breakNode) { + addStatement(breakNode); + return false; } @Override - public Node enterContinueNode(final ContinueNode continueNode) { - return enterBreakOrContinue(continueNode); + public Node leaveCallNode(final CallNode callNode) { + return checkEval(callNode.setFunction(markerFunction(callNode.getFunction()))); } @Override - public Node enterDoWhileNode(final DoWhileNode doWhileNode) { - return enterWhileNode(doWhileNode); + public Node leaveCatchNode(final CatchNode catchNode) { + return addStatement(catchNode); } @Override - public Node leaveDoWhileNode(final DoWhileNode doWhileNode) { - return leaveWhileNode(doWhileNode); + public boolean enterContinueNode(final ContinueNode continueNode) { + addStatement(continueNode); + return false; } @Override - public Node enterEmptyNode(final EmptyNode emptyNode) { - return null; + public boolean enterEmptyNode(final EmptyNode emptyNode) { + return false; } @Override public Node leaveExecuteNode(final ExecuteNode executeNode) { final Node expr = executeNode.getExpression(); + ExecuteNode node = executeNode; + + final FunctionNode currentFunction = getLexicalContext().getCurrentFunction(); - if (getCurrentFunctionNode().isProgram()) { + if (currentFunction.isProgram()) { if (!(expr instanceof Block) || expr instanceof FunctionNode) { // it's not a block, but can be a function if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) { - executeNode.setExpression(new BinaryNode(executeNode.getSource(), Token.recast(executeNode.getToken(), TokenType.ASSIGN), - getCurrentFunctionNode().getResultNode(), - expr)); + node = executeNode.setExpression( + new BinaryNode( + executeNode.getSource(), + Token.recast( + executeNode.getToken(), + TokenType.ASSIGN), + compilerConstant(RETURN), + expr)); } } } - copyTerminal(executeNode, executeNode.getExpression()); - addStatement(executeNode); - - return executeNode; - } - - @Override - public Node enterForNode(final ForNode forNode) { - nest(forNode); - return forNode; + return addStatement(node); } @Override public Node leaveForNode(final ForNode forNode) { - final Node test = forNode.getTest(); - final Block body = forNode.getBody(); - - if (!forNode.isForIn() && test == null) { - setHasGoto(forNode); - } - - final boolean escapes = controlFlowEscapes(body); - if (escapes) { - setTerminal(body, false); - } - - // pop the loop from the loop context - unnest(forNode); + ForNode newForNode = forNode; + final Node test = forNode.getTest(); if (!forNode.isForIn() && conservativeAlwaysTrue(test)) { - forNode.setTest(null); - setHasGoto(forNode); - setTerminal(forNode, !escapes); + newForNode = forNode.setTest(getLexicalContext(), null); } - addStatement(forNode); - - return forNode; + return addStatement(checkEscape(newForNode)); } @Override - public Node enterFunctionNode(final FunctionNode functionNode) { - LOG.info("START FunctionNode: " + functionNode.getName()); - - if (functionNode.isLazy()) { - LOG.info("LAZY: " + functionNode.getName()); - return null; - } - lexicalContext.push(functionNode); - initFunctionNode(functionNode); - - nest(functionNode); - - /* - * As we are evaluating a nested structure, we need to store the - * statement list for the surrounding block and restore it when the - * function is done - */ - final List<Node> savedStatements = statements; - final Node savedLastStatement = lastStatement; + public boolean enterFunctionNode(final FunctionNode functionNode) { + return !functionNode.isLazy(); + } - statements = new ArrayList<>(); - lastStatement = null; + @Override + public Node leaveFunctionNode(final FunctionNode functionNode) { + LOG.info("END FunctionNode: ", functionNode.getName()); + return functionNode.setState(getLexicalContext(), CompilationState.LOWERED); + } - if (functionNode.needsSelfSymbol()) { - //function needs to start with var funcIdent = __callee_; - statements.add(functionNode.getSelfSymbolInit().accept(this)); - } + @Override + public Node leaveIfNode(final IfNode ifNode) { + return addStatement(ifNode); + } - NodeVisitor visitor = this; - try { - //do the statements - this fills the block with code - boolean needsInitialEvalResult = functionNode.isProgram(); - for (final Node statement : functionNode.getStatements()) { - // If this function is a program, then insert an assignment to the initial eval result after all - // function declarations. - if(needsInitialEvalResult && !(statement instanceof LineNumberNode || (statement instanceof VarNode && ((VarNode)statement).isFunctionDeclaration()))) { - addInitialEvalResult(functionNode); - needsInitialEvalResult = false; - } - statement.accept(visitor); - //If there are unused terminated endpoints in the function, we need - // to add a "return undefined" in those places for correct semantics - LOG.info("Checking lastStatement="+lastStatement+" for terminal flags"); - if (lastStatement != null && lastStatement.hasTerminalFlags()) { - copyTerminal(functionNode, lastStatement); - assert !needsInitialEvalResult; - visitor = new DeadCodeVarDeclarationVisitor(); - } - } - if(needsInitialEvalResult) { - addInitialEvalResult(functionNode); - } - functionNode.setStatements(statements); + @Override + public Node leaveLabelNode(final LabelNode labelNode) { + return addStatement(labelNode); + } - if (!functionNode.isTerminal()) { - guaranteeReturn(functionNode); - } - } finally { - statements = savedStatements; - lastStatement = savedLastStatement; - } + @Override + public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) { + addStatement(lineNumberNode); // don't put it in lastStatement cache + return false; + } - LOG.info("END FunctionNode: " + functionNode.getName()); - unnest(functionNode); - lexicalContext.pop(functionNode); + @Override + public Node leaveReturnNode(final ReturnNode returnNode) { + addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor + return returnNode; + } - functionNode.setState(CompilationState.LOWERED); - return null; + @Override + public Node leaveSwitchNode(final SwitchNode switchNode) { + return addStatement(switchNode); } - /** - * This visitor is used to go over statements after a terminal statement. Those statements are dead code, but the - * var declarations in them still have the effect of declaring a local variable on the function level. Therefore, - * they aren't really dead code and must be preserved. Note that they're only preserved as no-op declarations; their - * initializers are wiped out as those are, in fact, dead code. - */ - private class DeadCodeVarDeclarationVisitor extends NodeOperatorVisitor { - DeadCodeVarDeclarationVisitor() { - } - - @Override - public Node enterVarNode(VarNode varNode) { - // Can't ever see a function declaration, as this visitor is only ever used after a terminal statement was - // encountered, and all function declarations precede any terminal statements. - assert !varNode.isFunctionDeclaration(); - if(varNode.getInit() == null) { - // No initializer, just pass it to Lower. - return varNode.accept(Lower.this); - } - // Wipe out the initializer and then pass it to Lower. - return varNode.setInit(null).accept(Lower.this); - } + @Override + public Node leaveThrowNode(final ThrowNode throwNode) { + addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor + return throwNode; } - private void addInitialEvalResult(final FunctionNode functionNode) { - new ExecuteNode(functionNode.getSource(), functionNode.getFirstToken(), functionNode.getFinish(), - getInitialEvalResult(functionNode)).accept(this); + private static Node ensureUniqueLabelsIn(final Node node) { + return node.accept(new NodeVisitor() { + @Override + public Node leaveDefault(final Node labelledNode) { + return labelledNode.ensureUniqueLabels(getLexicalContext()); + } + }); } - /** - * Result of initial result of evaluating a particular program, which is either the last function it declares, or - * undefined if it doesn't declare any functions. - * @param program - * @return the initial result of evaluating the program - */ - private static Node getInitialEvalResult(final FunctionNode program) { - IdentNode lastFnName = null; - for (final FunctionNode fn : program.getDeclaredFunctions()) { - assert fn.isDeclared(); - final IdentNode fnName = fn.getIdent(); - if(fnName != null) { - lastFnName = fnName; + private static List<Node> copyFinally(final Block finallyBody) { + final List<Node> newStatements = new ArrayList<>(); + for (final Node statement : finallyBody.getStatements()) { + newStatements.add(ensureUniqueLabelsIn(statement)); + if (statement.hasTerminalFlags()) { + return newStatements; } } - return lastFnName != null ? new IdentNode(lastFnName) : LiteralNode.newInstance(program, ScriptRuntime.UNDEFINED); + return newStatements; } - @Override - public Node enterIfNode(final IfNode ifNode) { - return nest(ifNode); - } + private Block catchAllBlock(final TryNode tryNode) { + final Source source = tryNode.getSource(); + final long token = tryNode.getToken(); + final int finish = tryNode.getFinish(); - @Override - public Node leaveIfNode(final IfNode ifNode) { - final Node pass = ifNode.getPass(); - final Node fail = ifNode.getFail(); + final IdentNode exception = new IdentNode(source, token, finish, getLexicalContext().getCurrentFunction().uniqueName("catch_all")); - if (pass.isTerminal() && fail != null && fail.isTerminal()) { - setTerminal(ifNode, true); - } + final Block catchBody = new Block(source, token, finish, new ThrowNode(source, token, finish, new IdentNode(exception))). + setIsTerminal(getLexicalContext(), true); //ends with throw, so terminal - addStatement(ifNode); - unnest(ifNode); + final CatchNode catchAllNode = new CatchNode(source, token, finish, new IdentNode(exception), null, catchBody); + final Block catchAllBlock = new Block(source, token, finish, catchAllNode); - return ifNode; + //catchallblock -> catchallnode (catchnode) -> exception -> throw + + return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower } - @Override - public Node enterLabelNode(LabelNode labelNode) { - final Block body = labelNode.getBody(); - body.accept(this); - copyTerminal(labelNode, body); - addStatement(labelNode); - return null; + private IdentNode compilerConstant(final CompilerConstants cc) { + final FunctionNode functionNode = getLexicalContext().getCurrentFunction(); + return new IdentNode(functionNode.getSource(), functionNode.getToken(), functionNode.getFinish(), cc.symbolName()); } - @Override - public Node enterLineNumberNode(final LineNumberNode lineNumberNode) { - addStatement(lineNumberNode, false); // don't put it in lastStatement cache - return null; + private static boolean isTerminal(final List<Node> statements) { + return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags(); } - @Override - public Node enterReturnNode(final ReturnNode returnNode) { - final TryNode tryNode = returnNode.getTryChain(); - final Node expr = returnNode.getExpression(); - - if (tryNode != null) { - //we are inside a try block - we don't necessarily have a result node yet. attr will do that. - if (expr != null) { - final Source source = getCurrentFunctionNode().getSource(); - - //we need to evaluate the result of the return in case it is complex while - //still in the try block, store it in a result value and return it afterwards - final long token = returnNode.getToken(); - final Node resultNode = new IdentNode(getCurrentFunctionNode().getResultNode()); - final Node assignResult = new BinaryNode(source, Token.recast(token, TokenType.ASSIGN), resultNode, expr); - - //add return_in_try = expr; to try block - new ExecuteNode(source, token, Token.descPosition(token), assignResult).accept(this); - - //splice in the finally code, inlining it here - if (copyFinally(tryNode, null)) { - return null; - } + /** + * Splice finally code into all endpoints of a trynode + * @param tryNode the try node + * @param list of rethrowing throw nodes from synthetic catch blocks + * @param finallyBody the code in the original finally block + * @return new try node after splicing finally code (same if nop) + */ + private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) { + final Source source = tryNode.getSource(); + final int finish = tryNode.getFinish(); - //make sure that the return node now returns 'return_in_try' - returnNode.setExpression(resultNode); - } else if (copyFinally(tryNode, null)) { - return null; - } - } else if (expr != null) { - returnNode.setExpression(expr.accept(this)); - } + assert tryNode.getFinallyBody() == null; - addStatement(returnNode); + final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor() { + final List<Node> insideTry = new ArrayList<>(); - return null; - } + @Override + public boolean enterDefault(final Node node) { + insideTry.add(node); + return true; + } - @Override - public Node leaveReturnNode(final ReturnNode returnNode) { - addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor - return returnNode; - } + @Override + public boolean enterFunctionNode(final FunctionNode functionNode) { + // do not enter function nodes - finally code should not be inlined into them + return false; + } - @Override - public Node enterSwitchNode(final SwitchNode switchNode) { - nest(switchNode); - return switchNode; - } + @Override + public Node leaveThrowNode(final ThrowNode throwNode) { + if (rethrows.contains(throwNode)) { + final List<Node> newStatements = copyFinally(finallyBody); + if (!isTerminal(newStatements)) { + newStatements.add(throwNode); + } + return new Block(source, throwNode.getToken(), throwNode.getFinish(), newStatements); + } + return throwNode; + } - @Override - public Node leaveSwitchNode(final SwitchNode switchNode) { - unnest(switchNode); + @Override + public Node leaveBreakNode(final BreakNode breakNode) { + return copy(breakNode, Lower.this.getLexicalContext().getBreakable(breakNode.getLabel())); + } - final List<CaseNode> cases = switchNode.getCases(); - final CaseNode defaultCase = switchNode.getDefaultCase(); + @Override + public Node leaveContinueNode(final ContinueNode continueNode) { + return copy(continueNode, Lower.this.getLexicalContext().getContinueTo(continueNode.getLabel())); + } - boolean allTerminal = !cases.isEmpty(); - for (final CaseNode caseNode : switchNode.getCases()) { - allTerminal &= caseNode.isTerminal(); - } + @Override + public Node leaveReturnNode(final ReturnNode returnNode) { + final Node expr = returnNode.getExpression(); + final List<Node> newStatements = new ArrayList<>(); + + final Node resultNode; + if (expr != null) { + //we need to evaluate the result of the return in case it is complex while + //still in the try block, store it in a result value and return it afterwards + resultNode = new IdentNode(Lower.this.compilerConstant(RETURN)); + newStatements.add(new ExecuteNode(new BinaryNode(source, Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr))); + } else { + resultNode = null; + } - if (allTerminal && defaultCase != null && defaultCase.isTerminal()) { - setTerminal(switchNode, true); - } + newStatements.addAll(copyFinally(finallyBody)); + if (!isTerminal(newStatements)) { + newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode)); + } - addStatement(switchNode); + return new ExecuteNode(new Block(source, returnNode.getToken(), getLexicalContext().getCurrentBlock().getFinish(), newStatements)); + } - return switchNode; - } + private Node copy(final Node endpoint, final Node targetNode) { + if (!insideTry.contains(targetNode)) { + final List<Node> newStatements = copyFinally(finallyBody); + if (!isTerminal(newStatements)) { + newStatements.add(endpoint); + } + return new ExecuteNode(new Block(source, endpoint.getToken(), finish, newStatements)); + } + return endpoint; + } + }); - @Override - public Node leaveThrowNode(final ThrowNode throwNode) { - addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor - return throwNode; + addStatement(newTryNode); + for (final Node statement : finallyBody.getStatements()) { + addStatement(statement); + } + + return newTryNode; } @Override - public Node enterTryNode(final TryNode tryNode) { - final Block finallyBody = tryNode.getFinallyBody(); - final long token = tryNode.getToken(); - final int finish = tryNode.getFinish(); - - nest(tryNode); + public Node leaveTryNode(final TryNode tryNode) { + final Block finallyBody = tryNode.getFinallyBody(); if (finallyBody == null) { - //do nothing if no finally exists - return tryNode; + return addStatement(tryNode); } /* - * We have a finally clause. + * create a new trynode + * if we have catches: * - * Transform to do finally tail duplication as follows: + * try try + * x try + * catch x + * y catch + * finally z y + * catchall + * rethrow * - * <pre> - * try { - * try_body - * } catch e1 { - * catchbody_1 - * } - * ... - * } catch en { - * catchbody_n - * } finally { - * finally_body - * } + * otheriwse * - * (where e1 ... en are optional) + * try try + * x x + * finally catchall + * y rethrow * - * turns into * - * try { - * try { - * try_body - * } catch e1 { - * catchbody1 - * //nothing inlined explicitly here, return, break other - * //terminals may inline the finally body - * ... - * } catch en { - * catchbody2 - * //nothing inlined explicitly here, return, break other - * //terminals may inline the finally body - * } - * } catch all ex { - * finally_body_inlined - * rethrow ex - * } - * finally_body_inlined - * </pre> + * now splice in finally code wherever needed * - * If tries are catches are terminal, visitors for return, break & - * continue will handle the tail duplications. Throw needs to be - * treated specially with the catchall as described in the above - * ASCII art. - * - * If the try isn't terminal we do the finally_body_inlined at the - * end. If the try is terminated with continue/break/return the - * existing visitor logic will inline the finally before that - * operation. if the try is terminated with a throw, the catches e1 - * ... en will have a chance to process the exception. If the - * appropriate catch e1..en is non terminal we fall through to the - * last finally_body_inlined. if the catch e1...en IS terminal with - * continue/break/return existing visitor logic will fix it. If they - * are terminal with another throw it goes to the catchall and the - * finally_body_inlined marked (*) will fix it before rethrowing - * whatever problem there was for identical semantic. */ - final Source source = getCurrentFunctionNode().getSource(); - - // if try node does not contain a catch we can skip creation of a new - // try node and just append our synthetic catch to the existing try node. - if (!tryNode.getCatchBlocks().isEmpty()) { - // insert an intermediate try-catch* node, where we move the body and all catch blocks. - // the original try node become a try-finally container for the new try-catch* node. - // because we don't clone (to avoid deep copy), we have to fix the block chain in the end. - final TryNode innerTryNode; - innerTryNode = new TryNode(source, token, finish, tryNode.getNext()); - innerTryNode.setBody(tryNode.getBody()); - innerTryNode.setCatchBlocks(tryNode.getCatchBlocks()); - - // set outer tryNode's body to innerTryNode - final Block outerBody; - outerBody = new Block(source, token, finish); - outerBody.setStatements(new ArrayList<Node>(Arrays.asList(innerTryNode))); - tryNode.setBody(outerBody); - tryNode.setCatchBlocks(null); - } - - // create a catch-all that inlines finally and rethrows - - final Block catchBlock = new Block(source, token, finish); - //this catch block should get define symbol + TryNode newTryNode; - final Block catchBody = new Block(source, token, finish); - final Node catchAllFinally = finallyBody.copy(); + final Block catchAll = catchAllBlock(tryNode); - catchBody.addStatement(new ExecuteNode(source, finallyBody.getToken(), finallyBody.getFinish(), catchAllFinally)); - setTerminal(catchBody, true); - - final CatchNode catchAllNode; - final IdentNode exception; - - exception = new IdentNode(source, token, finish, getCurrentFunctionNode().uniqueName("catch_all")); - catchAllNode = new CatchNode(source, token, finish, new IdentNode(exception), null, catchBody); - catchAllNode.setIsSyntheticRethrow(); + final List<ThrowNode> rethrows = new ArrayList<>(); + catchAll.accept(new NodeVisitor() { + @Override + public boolean enterThrowNode(final ThrowNode throwNode) { + rethrows.add(throwNode); + return true; + } + }); + assert rethrows.size() == 1; - catchBlock.addStatement(catchAllNode); + if (tryNode.getCatchBlocks().isEmpty()) { + newTryNode = tryNode.setFinallyBody(null); + } else { + Block outerBody = new Block(tryNode.getSource(), tryNode.getToken(), tryNode.getFinish(), new ArrayList<Node>(Arrays.asList(tryNode.setFinallyBody(null)))); + newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null); + } - // replace all catches of outer tryNode with the catch-all - tryNode.setCatchBlocks(new ArrayList<>(Arrays.asList(catchBlock))); + newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null); /* - * We leave the finally block for the original try in place for now - * so that children visitations will work. It is removed and placed - * afterwards in the else case below, after all children are visited + * Now that the transform is done, we have to go into the try and splice + * the finally block in front of any statement that is outside the try */ - - return tryNode; - } - - @Override - public Node leaveTryNode(final TryNode tryNode) { - final Block finallyBody = tryNode.getFinallyBody(); - - boolean allTerminal = tryNode.getBody().isTerminal() && (finallyBody == null || finallyBody.isTerminal()); - - for (final Block catchBlock : tryNode.getCatchBlocks()) { - allTerminal &= catchBlock.isTerminal(); - } - - tryNode.setIsTerminal(allTerminal); - - addStatement(tryNode); - unnest(tryNode); - - // if finally body is present, place it after the tryNode - if (finallyBody != null) { - tryNode.setFinallyBody(null); - addStatement(finallyBody); - } - - return tryNode; + return spliceFinally(newTryNode, rethrows, finallyBody); } @Override public Node leaveVarNode(final VarNode varNode) { addStatement(varNode); + if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && getLexicalContext().getCurrentFunction().isProgram()) { + new ExecuteNode(varNode.getSource(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this); + } return varNode; } @Override - public Node enterWhileNode(final WhileNode whileNode) { - return nest(whileNode); - } - - @Override public Node leaveWhileNode(final WhileNode whileNode) { final Node test = whileNode.getTest(); + final Block body = whileNode.getBody(); - if (test == null) { - setHasGoto(whileNode); - } - - final Block body = whileNode.getBody(); - final boolean escapes = controlFlowEscapes(body); - if (escapes) { - setTerminal(body, false); + if (conservativeAlwaysTrue(test)) { + //turn it into a for node without a test. + final ForNode forNode = (ForNode)new ForNode(whileNode.getSource(), whileNode.getToken(), whileNode.getFinish(), null, null, body, null, ForNode.IS_FOR).accept(this); + getLexicalContext().replace(whileNode, forNode); + return forNode; } - Node node = whileNode; - - if (body.isTerminal()) { - if (whileNode instanceof DoWhileNode) { - setTerminal(whileNode, true); - } else if (conservativeAlwaysTrue(test)) { - node = new ForNode(whileNode.getSource(), whileNode.getToken(), whileNode.getFinish()); - ((ForNode)node).setBody(body); - node.accept(this); - setTerminal(node, !escapes); - } - } - - // pop the loop from the loop context - unnest(whileNode); - addStatement(node); - - return node; + return addStatement(checkEscape(whileNode)); } @Override public Node leaveWithNode(final WithNode withNode) { - if (withNode.getBody().isTerminal()) { - setTerminal(withNode, true); - } - addStatement(withNode); - - return withNode; + return addStatement(withNode); } @Override @@ -741,23 +549,25 @@ final class Lower extends NodeOperatorVisitor { * * @param callNode call node to check if it's an eval */ - private void checkEval(final CallNode callNode) { + private CallNode checkEval(final CallNode callNode) { if (callNode.getFunction() instanceof IdentNode) { final List<Node> args = callNode.getArgs(); final IdentNode callee = (IdentNode)callNode.getFunction(); // 'eval' call with at least one argument - if (args.size() >= 1 && EVAL.tag().equals(callee.getName())) { - final CallNode.EvalArgs evalArgs = + if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) { + final FunctionNode currentFunction = getLexicalContext().getCurrentFunction(); + return callNode.setEvalArgs( new CallNode.EvalArgs( - args.get(0).copy().accept(this), //clone as we use this for the "is eval case". original evaluated separately for "is not eval case" - getCurrentFunctionNode().getThisNode(), + ensureUniqueLabelsIn(args.get(0)).accept(this), + compilerConstant(THIS), evalLocation(callee), - getCurrentFunctionNode().isStrictMode()); - callNode.setEvalArgs(evalArgs); + currentFunction.isStrict())); } } + + return callNode; } private static boolean conservativeAlwaysTrue(final Node node) { @@ -773,7 +583,7 @@ final class Lower extends NodeOperatorVisitor { * @param loopBody the loop body to check * @return true if control flow may escape the loop */ - private boolean controlFlowEscapes(final Node loopBody) { + private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) { final List<Node> escapes = new ArrayList<>(); loopBody.accept(new NodeVisitor() { @@ -786,7 +596,7 @@ final class Lower extends NodeOperatorVisitor { @Override public Node leaveContinueNode(final ContinueNode node) { // all inner loops have been popped. - if (nesting.contains(node.getTargetNode())) { + if (lex.contains(lex.getContinueTo(node.getLabel()))) { escapes.add(node); } return node; @@ -796,135 +606,23 @@ final class Lower extends NodeOperatorVisitor { return !escapes.isEmpty(); } - private void guaranteeReturn(final FunctionNode functionNode) { - Node resultNode; - - if (functionNode.isProgram()) { - resultNode = functionNode.getResultNode(); // the eval result, symbol assigned in Attr - } else { - if (lastStatement != null && lastStatement.isTerminal() || lastStatement instanceof ReturnNode) { - return; //already in place or not needed, as it should be for a non-undefined returning function - } - resultNode = LiteralNode.newInstance(functionNode, ScriptRuntime.UNDEFINED); - } - - //create a return statement - final Node returnNode = new ReturnNode(functionNode.getSource(), functionNode.getLastToken(), functionNode.getFinish(), resultNode, null); - returnNode.accept(this); - } - - - private Node nest(final Node node) { - LOG.info("Nesting: " + node); - LOG.indent(); - nesting.push(node); - return node; - } - - private void unnest(final Node node) { - LOG.unindent(); - assert nesting.getFirst() == node : "inconsistent nesting order : " + nesting.getFirst() + " != " + node; - LOG.info("Unnesting: " + nesting); - nesting.pop(); - } - - private static void setTerminal(final Node node, final boolean isTerminal) { - LOG.info("terminal = " + isTerminal + " for " + node); - node.setIsTerminal(isTerminal); - } - - private static void setHasGoto(final Node node) { //, final boolean hasGoto) { - LOG.info("hasGoto = true for " + node); - node.setHasGoto(); - } - - private static void copyTerminal(final Node node, final Node sourceNode) { - LOG.info("copy terminal flags " + sourceNode + " -> " + node); - node.copyTerminalFlags(sourceNode); - } - - private void addStatement(final Node statement, final boolean storeInLastStatement) { - LOG.info("add statement = " + statement + " (lastStatement = " + lastStatement + ")"); - statements.add(statement); - if (storeInLastStatement) { - lastStatement = statement; - } - } - - private void addStatement(final Node statement) { - addStatement(statement, true); - } - - /** - * Determine if Try block is inside target block. - * - * @param tryNode Try node to test. - * @param target Target block. - * - * @return true if try block is inside the target, false otherwise. - */ - private boolean isNestedTry(final TryNode tryNode, final Block target) { - for(Iterator<Block> blocks = lexicalContext.getBlocks(getCurrentBlock()); blocks.hasNext();) { - final Block block = blocks.next(); - if(block == target) { - return false; - } - if(tryNode.isChildBlock(block)) { - return true; - } + private LoopNode checkEscape(final LoopNode loopNode) { + final LexicalContext lc = getLexicalContext(); + final boolean escapes = controlFlowEscapes(lc, loopNode.getBody()); + if (escapes) { + return loopNode. + setBody(lc, loopNode.getBody().setIsTerminal(lc, false)). + setControlFlowEscapes(lc, escapes); } - return false; + return loopNode; } - /** - * Clones the body of the try finallys up to the target block. - * - * @param node first try node in the chain. - * @param targetNode target block of the break/continue statement or null for return - * - * @return true if terminates. - */ - private boolean copyFinally(final TryNode node, final Node targetNode) { - Block target = null; - - if (targetNode instanceof Block) { - target = (Block)targetNode; - } - - for (TryNode tryNode = node; tryNode != null; tryNode = tryNode.getNext()) { - if (target != null && !isNestedTry(tryNode, target)) { - return false; - } - - Block finallyBody = tryNode.getFinallyBody(); - if (finallyBody == null) { - continue; - } - - finallyBody = (Block)finallyBody.copy(); - final boolean hasTerminalFlags = finallyBody.hasTerminalFlags(); - - new ExecuteNode(finallyBody.getSource(), finallyBody.getToken(), finallyBody.getFinish(), finallyBody).accept(this); - if (hasTerminalFlags) { - getCurrentBlock().copyTerminalFlags(finallyBody); - return true; - } - } - - return false; - } - - private Node enterBreakOrContinue(final LabeledNode labeledNode) { - final TryNode tryNode = labeledNode.getTryChain(); - if (tryNode != null && copyFinally(tryNode, labeledNode.getTargetNode())) { - return null; - } - addStatement(labeledNode); - return null; + private Node addStatement(final Node statement) { + ((BlockLexicalContext)getLexicalContext()).appendStatement(statement); + return statement; } - /** * An internal expression has a symbol that is tagged internal. Check if * this is such a node @@ -939,40 +637,21 @@ final class Lower extends NodeOperatorVisitor { /** * Is this an assignment to the special variable that hosts scripting eval - * results? + * results, i.e. __return__? * * @param expression expression to check whether it is $evalresult = X * @return true if an assignment to eval result, false otherwise */ - private boolean isEvalResultAssignment(final Node expression) { + private static boolean isEvalResultAssignment(final Node expression) { Node e = expression; - if (e.tokenType() == TokenType.DISCARD) { - e = ((UnaryNode)expression).rhs(); - } - final Node resultNode = getCurrentFunctionNode().getResultNode(); - return e instanceof BinaryNode && ((BinaryNode)e).lhs().equals(resultNode); - } - - /** - * Prepare special function nodes. - * TODO : only create those that are needed. - * TODO : make sure slot numbering is not hardcoded in {@link CompilerConstants} - now creation order is significant - */ - private static void initFunctionNode(final FunctionNode functionNode) { - final Source source = functionNode.getSource(); - final long token = functionNode.getToken(); - final int finish = functionNode.getFinish(); - - functionNode.setThisNode(new IdentNode(source, token, finish, THIS.tag())); - functionNode.setScopeNode(new IdentNode(source, token, finish, SCOPE.tag())); - functionNode.setResultNode(new IdentNode(source, token, finish, SCRIPT_RETURN.tag())); - functionNode.setCalleeNode(new IdentNode(source, token, finish, CALLEE.tag())); - if (functionNode.isVarArg()) { - functionNode.setVarArgsNode(new IdentNode(source, token, finish, VARARGS.tag())); - if (functionNode.needsArguments()) { - functionNode.setArgumentsNode(new IdentNode(source, token, finish, ARGUMENTS.tag())); + assert e.tokenType() != TokenType.DISCARD; //there are no discards this early anymore + if (e instanceof BinaryNode) { + final Node lhs = ((BinaryNode)e).lhs(); + if (lhs instanceof IdentNode) { + return ((IdentNode)lhs).getName().equals(RETURN.symbolName()); } } + return false; } } diff --git a/src/jdk/nashorn/internal/codegen/MethodEmitter.java b/src/jdk/nashorn/internal/codegen/MethodEmitter.java index ae40ed33..4fbb57a6 100644 --- a/src/jdk/nashorn/internal/codegen/MethodEmitter.java +++ b/src/jdk/nashorn/internal/codegen/MethodEmitter.java @@ -53,9 +53,12 @@ import static jdk.internal.org.objectweb.asm.Opcodes.NEW; import static jdk.internal.org.objectweb.asm.Opcodes.PUTFIELD; import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; +import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS_DEBUGGER; +import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; import static jdk.nashorn.internal.codegen.CompilerConstants.className; import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; @@ -67,6 +70,7 @@ import java.lang.reflect.Array; import java.util.ArrayDeque; import java.util.EnumSet; import java.util.Iterator; +import java.util.List; import jdk.internal.dynalink.support.NameCodec; import jdk.internal.org.objectweb.asm.Handle; import jdk.internal.org.objectweb.asm.MethodVisitor; @@ -79,14 +83,14 @@ import jdk.nashorn.internal.codegen.types.NumericType; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.RuntimeNode; -import jdk.nashorn.internal.ir.SplitNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.runtime.ArgumentSetter; +import jdk.nashorn.internal.runtime.Debug; import jdk.nashorn.internal.runtime.DebugLogger; import jdk.nashorn.internal.runtime.JSType; -import jdk.nashorn.internal.runtime.Scope; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.linker.Bootstrap; @@ -116,10 +120,10 @@ public class MethodEmitter implements Emitter { private final ClassEmitter classEmitter; /** FunctionNode representing this method, or null if none exists */ - private FunctionNode functionNode; + protected FunctionNode functionNode; - /** SplitNode representing the current split, or null if none exists */ - private SplitNode splitNode; + /** Check whether this emitter ever has a function return point */ + private boolean hasReturn; /** The script environment */ private final ScriptEnvironment env; @@ -203,7 +207,7 @@ public class MethodEmitter implements Emitter { @Override public String toString() { - return "methodEmitter: " + (functionNode == null ? method : functionNode.getName()).toString() + ' ' + stack; + return "methodEmitter: " + (functionNode == null ? method : functionNode.getName()).toString() + ' ' + Debug.id(this); } /** @@ -476,8 +480,8 @@ public class MethodEmitter implements Emitter { String name = symbol.getName(); - if (name.equals(THIS.tag())) { - name = THIS_DEBUGGER.tag(); + if (name.equals(THIS.symbolName())) { + name = THIS_DEBUGGER.symbolName(); } method.visitLocalVariable(name, symbol.getSymbolType().getDescriptor(), null, start, end, symbol.getSlot()); @@ -654,7 +658,7 @@ public class MethodEmitter implements Emitter { * @return this method emitter */ MethodEmitter loadConstants() { - getStatic(classEmitter.getUnitClassName(), CONSTANTS.tag(), CONSTANTS.descriptor()); + getStatic(classEmitter.getUnitClassName(), CONSTANTS.symbolName(), CONSTANTS.descriptor()); assert peekType().isArray() : peekType(); return this; } @@ -669,7 +673,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter loadUndefined(final Type type) { - debug("load undefined " + type); + debug("load undefined ", type); pushType(type.loadUndefined(method)); return this; } @@ -681,7 +685,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter loadEmpty(final Type type) { - debug("load empty " + type); + debug("load empty ", type); pushType(type.loadEmpty(method)); return this; } @@ -814,7 +818,7 @@ public class MethodEmitter implements Emitter { } /** - * Push an local variable to the stack. If the symbol representing + * Push a local variable to the stack. If the symbol representing * the local variable doesn't have a slot, this is a NOP * * @param symbol the symbol representing the local variable. @@ -835,13 +839,13 @@ public class MethodEmitter implements Emitter { if (functionNode.needsArguments()) { // ScriptObject.getArgument(int) on arguments debug("load symbol", symbol.getName(), " arguments index=", index); - loadArguments(); + loadCompilerConstant(ARGUMENTS); load(index); ScriptObject.GET_ARGUMENT.invoke(this); } else { // array load from __varargs__ debug("load symbol", symbol.getName(), " array index=", index); - loadVarArgs(); + loadCompilerConstant(VARARGS); load(symbol.getFieldIndex()); arrayload(); } @@ -870,48 +874,13 @@ public class MethodEmitter implements Emitter { if(functionNode == null) { return slot == CompilerConstants.JAVA_THIS.slot(); } - final int thisSlot = functionNode.getThisNode().getSymbol().getSlot(); + final int thisSlot = compilerConstant(THIS).getSlot(); assert !functionNode.needsCallee() || thisSlot == 1; // needsCallee -> thisSlot == 1 assert functionNode.needsCallee() || thisSlot == 0; // !needsCallee -> thisSlot == 0 return slot == thisSlot; } /** - * Push the this object to the stack. - * - * @return the method emitter - */ - MethodEmitter loadThis() { - load(functionNode.getThisNode().getSymbol()); - return this; - } - - /** - * Push the scope object to the stack. - * - * @return the method emitter - */ - MethodEmitter loadScope() { - if (peekType() == Type.SCOPE) { - dup(); - return this; - } - load(functionNode.getScopeNode().getSymbol()); - return this; - } - - /** - * Push the return object to the stack. - * - * @return the method emitter - */ - MethodEmitter loadResult() { - load(functionNode.getResultNode().getSymbol()); - return this; - } - - - /** * Push a method handle to the stack * * @param className class name @@ -927,62 +896,33 @@ public class MethodEmitter implements Emitter { return this; } - /** - * Push the varargs object to the stack - * - * @return the method emitter - */ - MethodEmitter loadVarArgs() { - debug("load var args " + functionNode.getVarArgsNode().getSymbol()); - return load(functionNode.getVarArgsNode().getSymbol()); + private Symbol compilerConstant(final CompilerConstants cc) { + return functionNode.getBody().getExistingSymbol(cc.symbolName()); } /** - * Push the arguments array to the stack - * - * @return the method emitter + * True if this method has a slot allocated for the scope variable (meaning, something in the method actually needs + * the scope). + * @return if this method has a slot allocated for the scope variable. */ - MethodEmitter loadArguments() { - debug("load arguments ", functionNode.getArgumentsNode().getSymbol()); - assert functionNode.getArgumentsNode().getSymbol().getSlot() != 0; - return load(functionNode.getArgumentsNode().getSymbol()); + boolean hasScope() { + return compilerConstant(SCOPE).hasSlot(); } - /** - * Push the callee object to the stack - * - * @return the method emitter - */ - MethodEmitter loadCallee() { - final Symbol calleeSymbol = functionNode.getCalleeNode().getSymbol(); - debug("load callee ", calleeSymbol); - assert calleeSymbol.getSlot() == 0 : "callee has wrong slot " + calleeSymbol.getSlot() + " in " + functionNode.getName(); - - return load(calleeSymbol); - } - - /** - * Pop the scope from the stack and store it in its predefined slot - */ - void storeScope() { - debug("store scope"); - store(functionNode.getScopeNode().getSymbol()); + MethodEmitter loadCompilerConstant(final CompilerConstants cc) { + final Symbol symbol = compilerConstant(cc); + if (cc == SCOPE && peekType() == Type.SCOPE) { + dup(); + return this; + } + debug("load compiler constant ", symbol); + return load(symbol); } - /** - * Pop the return from the stack and store it in its predefined slot - */ - void storeResult() { - debug("store result"); - store(functionNode.getResultNode().getSymbol()); - } - - /** - * Pop the arguments array from the stack and store it in its predefined slot - */ - void storeArguments() { - debug("store arguments"); - store(functionNode.getArgumentsNode().getSymbol()); + void storeCompilerConstant(final CompilerConstants cc) { + final Symbol symbol = compilerConstant(cc); + debug("store compiler constant ", symbol); + store(symbol); } /** @@ -1030,13 +970,13 @@ public class MethodEmitter implements Emitter { final int index = symbol.getFieldIndex(); if (functionNode.needsArguments()) { debug("store symbol", symbol.getName(), " arguments index=", index); - loadArguments(); + loadCompilerConstant(ARGUMENTS); load(index); ArgumentSetter.SET_ARGUMENT.invoke(this); } else { // varargs without arguments object - just do array store to __varargs__ debug("store symbol", symbol.getName(), " array index=", index); - loadVarArgs(); + loadCompilerConstant(VARARGS); load(index); ArgumentSetter.SET_ARRAY_ELEMENT.invoke(this); } @@ -1144,7 +1084,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter newarray(final ArrayType arrayType) { - debug("newarray ", "arrayType=" + arrayType); + debug("newarray ", "arrayType=", arrayType); popType(Type.INT); //LENGTH pushType(arrayType.newarray(method)); return this; @@ -1223,7 +1163,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter invokespecial(final String className, final String methodName, final String methodDescriptor) { - debug("invokespecial", className + "." + methodName + methodDescriptor); + debug("invokespecial", className, ".", methodName, methodDescriptor); return invoke(INVOKESPECIAL, className, methodName, methodDescriptor, true); } @@ -1237,7 +1177,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter invokevirtual(final String className, final String methodName, final String methodDescriptor) { - debug("invokevirtual", className + "." + methodName + methodDescriptor + " " + stack); + debug("invokevirtual", className, ".", methodName, methodDescriptor, " ", stack); return invoke(INVOKEVIRTUAL, className, methodName, methodDescriptor, true); } @@ -1251,7 +1191,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter invokestatic(final String className, final String methodName, final String methodDescriptor) { - debug("invokestatic", className + "." + methodName + methodDescriptor); + debug("invokestatic", className, ".", methodName, methodDescriptor); invoke(INVOKESTATIC, className, methodName, methodDescriptor, false); return this; } @@ -1284,7 +1224,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter invokeinterface(final String className, final String methodName, final String methodDescriptor) { - debug("invokeinterface", className + "." + methodName + methodDescriptor); + debug("invokeinterface", className, ".", methodName, methodDescriptor); return invoke(INVOKEINTERFACE, className, methodName, methodDescriptor, true); } @@ -1336,15 +1276,20 @@ public class MethodEmitter implements Emitter { */ void conditionalJump(final Condition cond, final boolean isCmpG, final Label trueLabel) { if (peekType().isCategory2()) { - debug("[ld]cmp isCmpG=" + isCmpG); + debug("[ld]cmp isCmpG=", isCmpG); pushType(get2n().cmp(method, isCmpG)); jump(Condition.toUnary(cond), trueLabel, 1); } else { - debug("if" + cond); + debug("if", cond); jump(Condition.toBinary(cond, peekType().isObject()), trueLabel, 2); } } + MethodEmitter registerReturn() { + this.hasReturn = true; + return this; + } + /** * Perform a non void return, popping the type from the stack * @@ -1385,22 +1330,7 @@ public class MethodEmitter implements Emitter { * * @param label destination label */ - void splitAwareGoto(final Label label) { - - if (splitNode != null) { - final int index = splitNode.getExternalTargets().indexOf(label); - - if (index > -1) { - loadScope(); - checkcast(Scope.class); - load(index + 1); - invoke(Scope.SET_SPLIT_STATE); - loadUndefined(Type.OBJECT); - _return(functionNode.getReturnType()); - return; - } - } - + void splitAwareGoto(final LexicalContext lc, final Label label) { _goto(label); } @@ -1595,7 +1525,7 @@ public class MethodEmitter implements Emitter { */ private void mergeStackTo(final Label label) { final ArrayDeque<Type> labelStack = label.getStack(); - //debug(labelStack == null ? " >> Control flow - first visit " + label : " >> Control flow - JOIN with " + labelStack + " at " + label); + //debug(labelStack == null ? " >> Control flow - first visit ", label : " >> Control flow - JOIN with ", labelStack, " at ", label); if (labelStack == null) { assert stack != null; label.setStack(stack.clone()); @@ -1788,7 +1718,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter dynamicNew(final int argCount, final int flags) { - debug("dynamic_new", "argcount=" + argCount); + debug("dynamic_new", "argcount=", argCount); final String signature = getDynamicSignature(Type.OBJECT, argCount); method.visitInvokeDynamicInsn("dyn:new", signature, LINKERBOOTSTRAP, flags); pushType(Type.OBJECT); //TODO fix result type @@ -1805,7 +1735,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter dynamicCall(final Type returnType, final int argCount, final int flags) { - debug("dynamic_call", "args=" + argCount, "returnType=" + returnType); + debug("dynamic_call", "args=", argCount, "returnType=", returnType); final String signature = getDynamicSignature(returnType, argCount); // +1 because the function itself is the 1st parameter for dynamic calls (what you call - call target) debug(" signature", signature); method.visitInvokeDynamicInsn("dyn:call", signature, LINKERBOOTSTRAP, flags); @@ -1824,7 +1754,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter dynamicRuntimeCall(final String name, final Type returnType, final RuntimeNode.Request request) { - debug("dynamic_runtime_call", name, "args=" + request.getArity(), "returnType=" + returnType); + debug("dynamic_runtime_call", name, "args=", request.getArity(), "returnType=", returnType); final String signature = getDynamicSignature(returnType, request.getArity()); debug(" signature", signature); method.visitInvokeDynamicInsn(name, signature, RUNTIMEBOOTSTRAP); @@ -1895,7 +1825,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter dynamicGetIndex(final Type result, final int flags, final boolean isMethod) { - debug("dynamic_get_index", peekType(1) + "[" + peekType() + "]"); + debug("dynamic_get_index", peekType(1), "[", peekType(), "]"); Type resultType = result; if (result.isBoolean()) { @@ -1931,7 +1861,7 @@ public class MethodEmitter implements Emitter { * @param flags call site flags for setter */ void dynamicSetIndex(final int flags) { - debug("dynamic_set_index", peekType(2) + "[" + peekType(1) + "] =", peekType()); + debug("dynamic_set_index", peekType(2), "[", peekType(1), "] =", peekType()); Type value = peekType(); if (value.isObject() || value.isBoolean()) { @@ -2031,7 +1961,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter getField(final String className, final String fieldName, final String fieldDescriptor) { - debug("getfield", "receiver=" + peekType(), className + "." + fieldName + fieldDescriptor); + debug("getfield", "receiver=", peekType(), className, ".", fieldName, fieldDescriptor); final Type receiver = popType(); assert receiver.isObject(); method.visitFieldInsn(GETFIELD, className, fieldName, fieldDescriptor); @@ -2049,7 +1979,7 @@ public class MethodEmitter implements Emitter { * @return the method emitter */ MethodEmitter getStatic(final String className, final String fieldName, final String fieldDescriptor) { - debug("getstatic", className + "." + fieldName + "." + fieldDescriptor); + debug("getstatic", className, ".", fieldName, ".", fieldDescriptor); method.visitFieldInsn(GETSTATIC, className, fieldName, fieldDescriptor); pushType(fieldType(fieldDescriptor)); return this; @@ -2063,7 +1993,7 @@ public class MethodEmitter implements Emitter { * @param fieldDescriptor field descriptor */ void putField(final String className, final String fieldName, final String fieldDescriptor) { - debug("putfield", "receiver=" + peekType(1), "value=" + peekType()); + debug("putfield", "receiver=", peekType(1), "value=", peekType()); popType(fieldType(fieldDescriptor)); popType(Type.OBJECT); method.visitFieldInsn(PUTFIELD, className, fieldName, fieldDescriptor); @@ -2077,7 +2007,7 @@ public class MethodEmitter implements Emitter { * @param fieldDescriptor field descriptor */ void putStatic(final String className, final String fieldName, final String fieldDescriptor) { - debug("putfield", "value=" + peekType()); + debug("putfield", "value=", peekType()); popType(fieldType(fieldDescriptor)); method.visitFieldInsn(PUTSTATIC, className, fieldName, fieldDescriptor); } @@ -2237,7 +2167,7 @@ public class MethodEmitter implements Emitter { } if (env != null) { //early bootstrap code doesn't have inited context yet - LOG.info(sb.toString()); + LOG.info(sb); if (DEBUG_TRACE_LINE == linePrefix) { new Throwable().printStackTrace(LOG.getOutputStream()); } @@ -2254,21 +2184,12 @@ public class MethodEmitter implements Emitter { this.functionNode = functionNode; } - /** - * Get the split node for this method emitter, if this is code - * generation due to splitting large methods - * - * @return split node - */ - SplitNode getSplitNode() { - return splitNode; + boolean hasReturn() { + return hasReturn; } - /** - * Set the split node for this method emitter - * @param splitNode split node - */ - void setSplitNode(final SplitNode splitNode) { - this.splitNode = splitNode; + List<Label> getExternalTargets() { + return null; } + } diff --git a/src/jdk/nashorn/internal/codegen/Namespace.java b/src/jdk/nashorn/internal/codegen/Namespace.java index 02fdb942..5de2fdf8 100644 --- a/src/jdk/nashorn/internal/codegen/Namespace.java +++ b/src/jdk/nashorn/internal/codegen/Namespace.java @@ -53,7 +53,7 @@ public class Namespace { */ public Namespace(final Namespace parent) { this.parent = parent; - directory = new HashMap<>(); + this.directory = new HashMap<>(); } /** @@ -65,10 +65,6 @@ public class Namespace { return parent; } - private HashMap<String, Integer> getDirectory() { - return directory; - } - /** * Create a uniqueName name in the namespace in the form base$n where n varies * . @@ -78,7 +74,7 @@ public class Namespace { */ public String uniqueName(final String base) { for (Namespace namespace = this; namespace != null; namespace = namespace.getParent()) { - final HashMap<String, Integer> namespaceDirectory = namespace.getDirectory(); + final HashMap<String, Integer> namespaceDirectory = namespace.directory; final Integer counter = namespaceDirectory.get(base); if (counter != null) { diff --git a/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java b/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java index 6e032e2c..b7514526 100644 --- a/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java +++ b/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java @@ -28,10 +28,10 @@ package jdk.nashorn.internal.codegen; import static jdk.nashorn.internal.codegen.Compiler.SCRIPTS_PACKAGE; import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE; import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_ARGUMENTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_MAP; import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.JAVA_THIS; import static jdk.nashorn.internal.codegen.CompilerConstants.JS_OBJECT_PREFIX; -import static jdk.nashorn.internal.codegen.CompilerConstants.MAP; import static jdk.nashorn.internal.codegen.CompilerConstants.className; import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; import static jdk.nashorn.internal.lookup.Lookup.MH; @@ -204,8 +204,8 @@ public final class ObjectClassGenerator { * @return The class name. */ public static String getClassName(final int fieldCount) { - return fieldCount != 0 ? SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.tag() + fieldCount : - SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.tag(); + return fieldCount != 0 ? SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName() + fieldCount : + SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName(); } /** @@ -218,7 +218,23 @@ public final class ObjectClassGenerator { * @return The class name. */ public static String getClassName(final int fieldCount, final int paramCount) { - return SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.tag() + fieldCount + SCOPE_MARKER + paramCount; + return SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName() + fieldCount + SCOPE_MARKER + paramCount; + } + + /** + * Returns the number of fields in the JavaScript scope class. Its name had to be generated using either + * {@link #getClassName(int)} or {@link #getClassName(int, int)}. + * @param clazz the JavaScript scope class. + * @return the number of fields in the scope class. + */ + public static int getFieldCount(Class<?> clazz) { + final String name = clazz.getSimpleName(); + final String prefix = JS_OBJECT_PREFIX.symbolName(); + if(prefix.equals(name)) { + return 0; + } + final int scopeMarker = name.indexOf(SCOPE_MARKER); + return Integer.parseInt(scopeMarker == -1 ? name.substring(prefix.length()) : name.substring(prefix.length(), scopeMarker)); } /** @@ -387,7 +403,7 @@ public final class ObjectClassGenerator { final MethodEmitter init = classEmitter.init(PropertyMap.class); init.begin(); init.load(Type.OBJECT, JAVA_THIS.slot()); - init.load(Type.OBJECT, MAP.slot()); + init.load(Type.OBJECT, INIT_MAP.slot()); init.invoke(constructorNoLookup(ScriptObject.class, PropertyMap.class)); return init; @@ -402,7 +418,7 @@ public final class ObjectClassGenerator { final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class); init.begin(); init.load(Type.OBJECT, JAVA_THIS.slot()); - init.load(Type.OBJECT, MAP.slot()); + init.load(Type.OBJECT, INIT_MAP.slot()); init.load(Type.OBJECT, INIT_SCOPE.slot()); init.invoke(constructorNoLookup(FunctionScope.class, PropertyMap.class, ScriptObject.class)); @@ -418,7 +434,7 @@ public final class ObjectClassGenerator { final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class, Object.class); init.begin(); init.load(Type.OBJECT, JAVA_THIS.slot()); - init.load(Type.OBJECT, MAP.slot()); + init.load(Type.OBJECT, INIT_MAP.slot()); init.load(Type.OBJECT, INIT_SCOPE.slot()); init.load(Type.OBJECT, INIT_ARGUMENTS.slot()); init.invoke(constructorNoLookup(FunctionScope.class, PropertyMap.class, ScriptObject.class, Object.class)); @@ -449,7 +465,7 @@ public final class ObjectClassGenerator { * @param className Name of JavaScript class. */ private static void newAllocate(final ClassEmitter classEmitter, final String className) { - final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.tag(), ScriptObject.class, PropertyMap.class); + final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.symbolName(), ScriptObject.class, PropertyMap.class); allocate.begin(); allocate._new(className); allocate.dup(); diff --git a/src/jdk/nashorn/internal/codegen/ObjectCreator.java b/src/jdk/nashorn/internal/codegen/ObjectCreator.java index e4e7b7da..a9c494cc 100644 --- a/src/jdk/nashorn/internal/codegen/ObjectCreator.java +++ b/src/jdk/nashorn/internal/codegen/ObjectCreator.java @@ -36,7 +36,7 @@ import jdk.nashorn.internal.runtime.PropertyMap; public abstract class ObjectCreator { /** Compile unit for this ObjectCreator, see CompileUnit */ - protected final CompileUnit compileUnit; + //protected final CompileUnit compileUnit; /** List of keys to initiate in this ObjectCreator */ protected final List<String> keys; @@ -66,7 +66,6 @@ public abstract class ObjectCreator { */ protected ObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final boolean isScope, final boolean hasArguments) { this.codegen = codegen; - this.compileUnit = codegen.getCurrentCompileUnit(); this.keys = keys; this.symbols = symbols; this.isScope = isScope; diff --git a/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java b/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java new file mode 100644 index 00000000..5fa4486a --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java @@ -0,0 +1,100 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.codegen; + +import java.util.ArrayList; +import java.util.List; + +import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; + +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.LexicalContext; +import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.runtime.Scope; + +/** + * Emitter used for splitting methods. Needs to keep track of if there are jump targets + * outside the current split node. All external jump targets encountered at method + * emission are logged, and {@code CodeGenerator#leaveSplitNode(SplitNode)} creates + * an appropriate jump table when the SplitNode has been iterated through + */ +public class SplitMethodEmitter extends MethodEmitter { + + private final SplitNode splitNode; + + private final List<Label> externalTargets = new ArrayList<>(); + + SplitMethodEmitter(final ClassEmitter classEmitter, final MethodVisitor mv, SplitNode splitNode) { + super(classEmitter, mv); + this.splitNode = splitNode; + } + + @Override + void splitAwareGoto(final LexicalContext lc, final Label label) { + assert splitNode != null; + final int index = findExternalTarget(lc, label); + if (index >= 0) { + loadCompilerConstant(SCOPE); + checkcast(Scope.class); + load(index + 1); + invoke(Scope.SET_SPLIT_STATE); + loadUndefined(Type.OBJECT); + _return(functionNode.getReturnType()); + return; + } + super.splitAwareGoto(lc, label); + } + + private int findExternalTarget(final LexicalContext lc, final Label label) { + final int index = externalTargets.indexOf(label); + + if (index >= 0) { + return index; + } + + if (lc.isExternalTarget(splitNode, label)) { + externalTargets.add(label); + return externalTargets.size() - 1; + } + return -1; + } + + @Override + MethodEmitter registerReturn() { + super.registerReturn(); + loadCompilerConstant(SCOPE); + checkcast(Scope.class); + load(0); + invoke(Scope.SET_SPLIT_STATE); + return this; + } + + @Override + final List<Label> getExternalTargets() { + return externalTargets; + } +} diff --git a/src/jdk/nashorn/internal/codegen/Splitter.java b/src/jdk/nashorn/internal/codegen/Splitter.java index f9a84f91..f482e4df 100644 --- a/src/jdk/nashorn/internal/codegen/Splitter.java +++ b/src/jdk/nashorn/internal/codegen/Splitter.java @@ -28,29 +28,18 @@ package jdk.nashorn.internal.codegen; import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX; import java.util.ArrayList; -import java.util.Deque; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import jdk.nashorn.internal.ir.Block; -import jdk.nashorn.internal.ir.BreakNode; -import jdk.nashorn.internal.ir.ContinueNode; -import jdk.nashorn.internal.ir.DoWhileNode; -import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; -import jdk.nashorn.internal.ir.LabelNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit; import jdk.nashorn.internal.ir.Node; -import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.SplitNode; -import jdk.nashorn.internal.ir.SwitchNode; -import jdk.nashorn.internal.ir.WhileNode; -import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.DebugLogger; import jdk.nashorn.internal.runtime.Source; @@ -64,7 +53,7 @@ final class Splitter extends NodeVisitor { private final Compiler compiler; /** IR to be broken down. */ - private final FunctionNode functionNode; + private FunctionNode outermost; /** Compile unit for the main script. */ private final CompileUnit outermostCompileUnit; @@ -72,8 +61,6 @@ final class Splitter extends NodeVisitor { /** Cache for calculated block weights. */ private final Map<Node, Long> weightCache = new HashMap<>(); - private final LexicalContext lexicalContext = new LexicalContext(); - /** Weight threshold for when to start a split. */ public static final long SPLIT_THRESHOLD = Options.getIntProperty("nashorn.compiler.splitter.threshold", 32 * 1024); @@ -88,70 +75,92 @@ final class Splitter extends NodeVisitor { */ public Splitter(final Compiler compiler, final FunctionNode functionNode, final CompileUnit outermostCompileUnit) { this.compiler = compiler; - this.functionNode = functionNode; + this.outermost = functionNode; this.outermostCompileUnit = outermostCompileUnit; } /** * Execute the split */ - void split() { + FunctionNode split(final FunctionNode fn) { + FunctionNode functionNode = fn; + if (functionNode.isLazy()) { - LOG.finest("Postponing split of '" + functionNode.getName() + "' as it's lazy"); - return; + LOG.finest("Postponing split of '", functionNode.getName(), "' as it's lazy"); + return functionNode; } - LOG.finest("Initiating split of '" + functionNode.getName() + "'"); + LOG.finest("Initiating split of '", functionNode.getName(), "'"); + + final LexicalContext lc = getLexicalContext(); long weight = WeighNodes.weigh(functionNode); + final boolean top = compiler.getFunctionNode() == outermost; if (weight >= SPLIT_THRESHOLD) { - LOG.finest("Splitting '" + functionNode.getName() + "' as its weight " + weight + " exceeds split threshold " + SPLIT_THRESHOLD); - - functionNode.accept(this); + LOG.finest("Splitting '", functionNode.getName(), "' as its weight ", weight, " exceeds split threshold ", SPLIT_THRESHOLD); + functionNode = (FunctionNode)functionNode.accept(this); if (functionNode.isSplit()) { // Weight has changed so weigh again, this time using block weight cache weight = WeighNodes.weigh(functionNode, weightCache); + functionNode = functionNode.setBody(lc, functionNode.getBody().setNeedsScope(lc)); } if (weight >= SPLIT_THRESHOLD) { - weight = splitBlock(functionNode, functionNode); - } - - if (functionNode.isSplit()) { - functionNode.accept(new SplitFlowAnalyzer()); + functionNode = functionNode.setBody(lc, splitBlock(functionNode.getBody(), functionNode)); + weight = WeighNodes.weigh(functionNode.getBody(), weightCache); } } - assert functionNode.getCompileUnit() == null : "compile unit already set"; + assert functionNode.getCompileUnit() == null : "compile unit already set for " + functionNode.getName(); - if (compiler.getFunctionNode() == functionNode) { //functionNode.isScript()) { + if (top) { assert outermostCompileUnit != null : "outermost compile unit is null"; - - functionNode.setCompileUnit(outermostCompileUnit); + functionNode = functionNode.setCompileUnit(lc, outermostCompileUnit); outermostCompileUnit.addWeight(weight + WeighNodes.FUNCTION_WEIGHT); } else { - functionNode.setCompileUnit(findUnit(weight)); + functionNode = functionNode.setCompileUnit(lc, findUnit(weight)); } - // Recursively split nested functions - functionNode.accept(new NodeOperatorVisitor() { + final Block body = functionNode.getBody(); + final List<FunctionNode> dc = directChildren(functionNode); + + final Block newBody = (Block)body.accept(new NodeVisitor() { + @Override + public boolean enterFunctionNode(final FunctionNode nestedFunction) { + return dc.contains(nestedFunction); + } + + @Override + public Node leaveFunctionNode(final FunctionNode nestedFunction) { + FunctionNode split = new Splitter(compiler, nestedFunction, outermostCompileUnit).split(nestedFunction); + getLexicalContext().replace(nestedFunction, split); + return split; + } + }); + functionNode = functionNode.setBody(lc, newBody); + + assert functionNode.getCompileUnit() != null; + + return functionNode.setState(lc, CompilationState.SPLIT); + } + + private static List<FunctionNode> directChildren(final FunctionNode functionNode) { + final List<FunctionNode> dc = new ArrayList<>(); + functionNode.accept(new NodeVisitor() { @Override - public Node enterFunctionNode(FunctionNode function) { - if(function == functionNode) { - // Don't process outermost function (it was already processed) but descend into it to find nested - // functions. - return function; + public boolean enterFunctionNode(final FunctionNode child) { + if (child == functionNode) { + return true; } - // Process a nested function - new Splitter(compiler, function, outermostCompileUnit).split(); - // Don't descend into a a nested function; Splitter.split() has taken care of nested-in-nested functions. - return null; + if (getLexicalContext().getParentFunction(child) == functionNode) { + dc.add(child); + } + return false; } }); - - functionNode.setState(CompilationState.SPLIT); + return dc; } /** @@ -170,8 +179,8 @@ final class Splitter extends NodeVisitor { * * @return new weight for the resulting block. */ - private long splitBlock(final Block block, final FunctionNode function) { - functionNode.setIsSplit(); + private Block splitBlock(final Block block, final FunctionNode function) { + getLexicalContext().setFlag(getLexicalContext().getCurrentFunction(), FunctionNode.IS_SPLIT); final List<Node> splits = new ArrayList<>(); List<Node> statements = new ArrayList<>(); @@ -186,7 +195,6 @@ final class Splitter extends NodeVisitor { statements = new ArrayList<>(); statementsWeight = 0; } - } if (statement.isTerminal()) { @@ -201,9 +209,7 @@ final class Splitter extends NodeVisitor { splits.add(createBlockSplitNode(block, function, statements, statementsWeight)); } - block.setStatements(splits); - - return WeighNodes.weigh(block, weightCache); + return block.setStatements(getLexicalContext(), splits); } /** @@ -218,51 +224,44 @@ final class Splitter extends NodeVisitor { final Source source = parent.getSource(); final long token = parent.getToken(); final int finish = parent.getFinish(); - final String name = function.uniqueName(SPLIT_PREFIX.tag()); + final String name = function.uniqueName(SPLIT_PREFIX.symbolName()); - final Block newBlock = new Block(source, token, finish); - newBlock.setFrame(new Frame(parent.getFrame())); - newBlock.setStatements(statements); + final Block newBlock = new Block(source, token, finish, statements); - final SplitNode splitNode = new SplitNode(name, functionNode, newBlock); - - splitNode.setCompileUnit(compiler.findUnit(weight + WeighNodes.FUNCTION_WEIGHT)); - - return splitNode; + return new SplitNode(name, newBlock, compiler.findUnit(weight + WeighNodes.FUNCTION_WEIGHT)); } @Override - public Node enterBlock(final Block block) { + public boolean enterBlock(final Block block) { if (block.isCatchBlock()) { - return null; + return false; } - lexicalContext.push(block); final long weight = WeighNodes.weigh(block, weightCache); if (weight < SPLIT_THRESHOLD) { weightCache.put(block, weight); - lexicalContext.pop(block); - return null; + return false; } - return block; + return true; } @Override public Node leaveBlock(final Block block) { assert !block.isCatchBlock(); + Block newBlock = block; + // Block was heavier than SLIT_THRESHOLD in enter, but a sub-block may have // been split already, so weigh again before splitting. long weight = WeighNodes.weigh(block, weightCache); if (weight >= SPLIT_THRESHOLD) { - weight = splitBlock(block, lexicalContext.getFunction(block)); + newBlock = splitBlock(block, getLexicalContext().getFunction(block)); + weight = WeighNodes.weigh(newBlock, weightCache); } - weightCache.put(block, weight); - - lexicalContext.pop(block); - return block; + weightCache.put(newBlock, weight); + return newBlock; } @SuppressWarnings("rawtypes") @@ -274,7 +273,7 @@ final class Splitter extends NodeVisitor { return literal; } - functionNode.setIsSplit(); + getLexicalContext().setFlag(getLexicalContext().getCurrentFunction(), FunctionNode.IS_SPLIT); if (literal instanceof ArrayLiteralNode) { final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode) literal; @@ -312,123 +311,12 @@ final class Splitter extends NodeVisitor { } @Override - public Node enterFunctionNode(final FunctionNode node) { - if(node == functionNode && !node.isLazy()) { - lexicalContext.push(node); - node.visitStatements(this); - lexicalContext.pop(node); - } - return null; - } - - static class SplitFlowAnalyzer extends NodeVisitor { - - /** Stack of visited Split nodes, deepest node first. */ - private final Deque<SplitNode> splitStack; - - /** Map of possible jump targets to containing split node */ - private final Map<Node,SplitNode> targetNodes = new HashMap<>(); - - SplitFlowAnalyzer() { - this.splitStack = new LinkedList<>(); - } - - @Override - public Node enterLabelNode(final LabelNode labelNode) { - registerJumpTarget(labelNode.getBreakNode()); - registerJumpTarget(labelNode.getContinueNode()); - return labelNode; - } - - @Override - public Node enterWhileNode(final WhileNode whileNode) { - registerJumpTarget(whileNode); - return whileNode; - } - - @Override - public Node enterDoWhileNode(final DoWhileNode doWhileNode) { - registerJumpTarget(doWhileNode); - return doWhileNode; - } - - @Override - public Node enterForNode(final ForNode forNode) { - registerJumpTarget(forNode); - return forNode; - } - - @Override - public Node enterSwitchNode(final SwitchNode switchNode) { - registerJumpTarget(switchNode); - return switchNode; - } - - @Override - public Node enterReturnNode(final ReturnNode returnNode) { - for (final SplitNode split : splitStack) { - split.setHasReturn(true); - } - return returnNode; - } - - @Override - public Node enterContinueNode(final ContinueNode continueNode) { - searchJumpTarget(continueNode.getTargetNode(), continueNode.getTargetLabel()); - return continueNode; - } - - @Override - public Node enterBreakNode(final BreakNode breakNode) { - searchJumpTarget(breakNode.getTargetNode(), breakNode.getTargetLabel()); - return breakNode; - } - - @Override - public Node enterSplitNode(final SplitNode splitNode) { - splitStack.addFirst(splitNode); - return splitNode; - } - - @Override - public Node leaveSplitNode(final SplitNode splitNode) { - assert splitNode == splitStack.peekFirst(); - splitStack.removeFirst(); - return splitNode; - } - - /** - * Register the split node containing a potential jump target. - * @param targetNode a potential target node. - */ - private void registerJumpTarget(final Node targetNode) { - final SplitNode splitNode = splitStack.peekFirst(); - if (splitNode != null) { - targetNodes.put(targetNode, splitNode); - } - } - - /** - * Check if a jump target is outside the current split node and its parent split nodes. - * @param targetNode the jump target node. - * @param targetLabel the jump target label. - */ - private void searchJumpTarget(final Node targetNode, final Label targetLabel) { - - final SplitNode targetSplit = targetNodes.get(targetNode); - // Note that targetSplit may be null, indicating that targetNode is in top level method. - // In this case we have to add the external jump target to all split nodes. - - for (final SplitNode split : splitStack) { - if (split == targetSplit) { - break; - } - final List<Label> externalTargets = split.getExternalTargets(); - if (!externalTargets.contains(targetLabel)) { - split.addExternalTarget(targetLabel); - } - } + public boolean enterFunctionNode(final FunctionNode node) { + //only go into the function node for this splitter. any subfunctions are rejected + if (node == outermost && !node.isLazy()) { + return true; } + return false; } } diff --git a/src/jdk/nashorn/internal/codegen/WeighNodes.java b/src/jdk/nashorn/internal/codegen/WeighNodes.java index 18bd9552..002cc902 100644 --- a/src/jdk/nashorn/internal/codegen/WeighNodes.java +++ b/src/jdk/nashorn/internal/codegen/WeighNodes.java @@ -35,7 +35,6 @@ import jdk.nashorn.internal.ir.BreakNode; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; -import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; @@ -101,7 +100,7 @@ final class WeighNodes extends NodeOperatorVisitor { * @param weightCache cache of already calculated block weights */ private WeighNodes(FunctionNode topFunction, final Map<Node, Long> weightCache) { - super(null, null); + super(); this.topFunction = topFunction; this.weightCache = weightCache; } @@ -123,13 +122,13 @@ final class WeighNodes extends NodeOperatorVisitor { } @Override - public Node enterBlock(final Block block) { + public boolean enterBlock(final Block block) { if (weightCache != null && weightCache.containsKey(block)) { weight += weightCache.get(block); - return null; + return false; } - return block; + return true; } @Override @@ -157,12 +156,6 @@ final class WeighNodes extends NodeOperatorVisitor { } @Override - public Node leaveDoWhileNode(final DoWhileNode doWhileNode) { - weight += LOOP_WEIGHT; - return doWhileNode; - } - - @Override public Node leaveExecuteNode(final ExecuteNode executeNode) { return executeNode; } @@ -174,15 +167,15 @@ final class WeighNodes extends NodeOperatorVisitor { } @Override - public Node enterFunctionNode(final FunctionNode functionNode) { - if(functionNode == topFunction) { + public boolean enterFunctionNode(final FunctionNode functionNode) { + if (functionNode == topFunction) { // the function being weighted; descend into its statements - functionNode.visitStatements(this); - } else { - // just a reference to inner function from outer function - weight += FUNC_EXPR_WEIGHT; + return true; +// functionNode.visitStatements(this); } - return null; + // just a reference to inner function from outer function + weight += FUNC_EXPR_WEIGHT; + return false; } @Override @@ -205,7 +198,7 @@ final class WeighNodes extends NodeOperatorVisitor { @SuppressWarnings("rawtypes") @Override - public Node enterLiteralNode(final LiteralNode literalNode) { + public boolean enterLiteralNode(final LiteralNode literalNode) { weight += LITERAL_WEIGHT; if (literalNode instanceof ArrayLiteralNode) { @@ -224,10 +217,10 @@ final class WeighNodes extends NodeOperatorVisitor { } } - return null; + return false; } - return literalNode; + return true; } @Override @@ -249,9 +242,9 @@ final class WeighNodes extends NodeOperatorVisitor { } @Override - public Node enterSplitNode(final SplitNode splitNode) { + public boolean enterSplitNode(final SplitNode splitNode) { weight += SPLIT_WEIGHT; - return null; + return false; } @Override diff --git a/src/jdk/nashorn/internal/codegen/types/BooleanType.java b/src/jdk/nashorn/internal/codegen/types/BooleanType.java index c27c0584..4331cb55 100644 --- a/src/jdk/nashorn/internal/codegen/types/BooleanType.java +++ b/src/jdk/nashorn/internal/codegen/types/BooleanType.java @@ -93,12 +93,6 @@ public final class BooleanType extends Type { } @Override - public Type loadEmpty(final MethodVisitor method) { - assert false : "unsupported operation"; - return null; - } - - @Override public void _return(final MethodVisitor method) { method.visitInsn(IRETURN); } diff --git a/src/jdk/nashorn/internal/codegen/types/IntType.java b/src/jdk/nashorn/internal/codegen/types/IntType.java index 9b859ddd..c282d109 100644 --- a/src/jdk/nashorn/internal/codegen/types/IntType.java +++ b/src/jdk/nashorn/internal/codegen/types/IntType.java @@ -241,12 +241,6 @@ class IntType extends BitwiseType { } @Override - public Type loadEmpty(final MethodVisitor method) { - assert false : "unsupported operation"; - return null; - } - - @Override public Type cmp(final MethodVisitor method, final boolean isCmpG) { assert false : "unsupported operation"; return null; diff --git a/src/jdk/nashorn/internal/codegen/types/LongType.java b/src/jdk/nashorn/internal/codegen/types/LongType.java index d3e00b67..b57ea6d3 100644 --- a/src/jdk/nashorn/internal/codegen/types/LongType.java +++ b/src/jdk/nashorn/internal/codegen/types/LongType.java @@ -217,12 +217,6 @@ class LongType extends BitwiseType { } @Override - public Type loadEmpty(final MethodVisitor method) { - assert false : "unsupported operation"; - return null; - } - - @Override public Type cmp(final MethodVisitor method, final boolean isCmpG) { return cmp(method); } diff --git a/src/jdk/nashorn/internal/codegen/types/NumberType.java b/src/jdk/nashorn/internal/codegen/types/NumberType.java index 4dc62c52..6a280b79 100644 --- a/src/jdk/nashorn/internal/codegen/types/NumberType.java +++ b/src/jdk/nashorn/internal/codegen/types/NumberType.java @@ -89,12 +89,6 @@ class NumberType extends NumericType { } @Override - public Type loadEmpty(final MethodVisitor method) { - assert false : "unsupported operation"; - return null; - } - - @Override public Type ldc(final MethodVisitor method, final Object c) { assert c instanceof Double; diff --git a/src/jdk/nashorn/internal/codegen/types/Type.java b/src/jdk/nashorn/internal/codegen/types/Type.java index 77558835..747793e8 100644 --- a/src/jdk/nashorn/internal/codegen/types/Type.java +++ b/src/jdk/nashorn/internal/codegen/types/Type.java @@ -616,6 +616,12 @@ public abstract class Type implements Comparable<Type>, BytecodeOps { return this; } + @Override + public Type loadEmpty(final MethodVisitor method) { + assert false : "unsupported operation"; + return null; + } + /** * Superclass logic for pop for all types * @@ -663,7 +669,6 @@ public abstract class Type implements Comparable<Type>, BytecodeOps { method.visitInsn(SWAP); } } - } /** @@ -841,12 +846,6 @@ public abstract class Type implements Comparable<Type>, BytecodeOps { } @Override - public Type loadEmpty(final MethodVisitor method) { - assert false : "unsupported operation"; - return null; - } - - @Override public Type convert(final MethodVisitor method, final Type to) { assert false : "unsupported operation"; return null; diff --git a/src/jdk/nashorn/internal/ir/AccessNode.java b/src/jdk/nashorn/internal/ir/AccessNode.java index b7b76684..2f739bf0 100644 --- a/src/jdk/nashorn/internal/ir/AccessNode.java +++ b/src/jdk/nashorn/internal/ir/AccessNode.java @@ -25,23 +25,18 @@ package jdk.nashorn.internal.ir; -import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS; - -import jdk.nashorn.internal.codegen.ObjectClassGenerator; 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.runtime.Source; /** * IR representation of a property access (period operator.) - * */ -public class AccessNode extends BaseNode implements TypeOverride<AccessNode> { +@Immutable +public final class AccessNode extends BaseNode { /** Property ident. */ - private IdentNode property; - - /** Does this node have a type override */ - private boolean hasCallSiteType; + private final IdentNode property; /** * Constructor @@ -53,49 +48,13 @@ public class AccessNode extends BaseNode implements TypeOverride<AccessNode> { * @param property property */ public AccessNode(final Source source, final long token, final int finish, final Node base, final IdentNode property) { - super(source, token, finish, base); - - this.start = base.getStart(); + super(source, token, finish, base, false, false); this.property = property.setIsPropertyName(); } - /** - * Copy constructor - * - * @param accessNode source node - */ - public AccessNode(final AccessNode accessNode) { - this(accessNode, new CopyState()); - } - - /** - * Internal copy constructor - * - * @param accessNode source node - * @param cs copy state - */ - protected AccessNode(final AccessNode accessNode, final CopyState cs) { - super(accessNode, cs); - this.property = (IdentNode)cs.existingOrCopy(accessNode.getProperty()); - } - - @Override - protected Node copy(final CopyState cs) { - return new AccessNode(this, cs); - } - - @Override - public boolean equals(final Object other) { - if (!super.equals(other)) { - return false; - } - final AccessNode accessNode = (AccessNode)other; - return property.equals(accessNode.getProperty()); - } - - @Override - public int hashCode() { - return super.hashCode() ^ property.hashCode(); + private AccessNode(final AccessNode accessNode, final Node base, final IdentNode property, final boolean isFunction, final boolean hasCallSiteType) { + super(accessNode, base, isFunction, hasCallSiteType); + this.property = property; } /** @@ -104,12 +63,11 @@ public class AccessNode extends BaseNode implements TypeOverride<AccessNode> { */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterAccessNode(this) != null) { - base = base.accept(visitor); - property = (IdentNode)property.accept(visitor); - return visitor.leaveAccessNode(this); + if (visitor.enterAccessNode(this)) { + return visitor.leaveAccessNode( + setBase(base.accept(visitor)). + setProperty((IdentNode)property.accept(visitor))); } - return this; } @@ -117,7 +75,7 @@ public class AccessNode extends BaseNode implements TypeOverride<AccessNode> { public void toString(final StringBuilder sb) { final boolean needsParen = tokenType().needsParens(getBase().tokenType(), true); - if (hasCallSiteType) { + if (hasCallSiteType()) { sb.append('{'); final String desc = getType().getDescriptor(); sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : getType().getDescriptor()); @@ -147,19 +105,34 @@ public class AccessNode extends BaseNode implements TypeOverride<AccessNode> { return property; } + private AccessNode setBase(final Node base) { + if (this.base == base) { + return this; + } + return new AccessNode(this, base, property, isFunction(), hasCallSiteType()); + } + + + private AccessNode setProperty(final IdentNode property) { + if (this.property == property) { + return this; + } + return new AccessNode(this, base, property, isFunction(), hasCallSiteType()); + } + @Override public AccessNode setType(final Type type) { - if (DEBUG_FIELDS && !Type.areEquivalent(getSymbol().getSymbolType(), type)) { - ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType()); - } - property = property.setType(type); + logTypeChange(type); getSymbol().setTypeOverride(type); //always a temp so this is fine. - hasCallSiteType = true; - return this; + return new AccessNode(this, base, property.setType(type), isFunction(), hasCallSiteType()); } @Override - public boolean canHaveCallSiteType() { - return true; //carried by the symbol and always the same nodetype==symboltype + public BaseNode setIsFunction() { + if (isFunction()) { + return this; + } + return new AccessNode(this, base, property, true, hasCallSiteType()); } + } diff --git a/src/jdk/nashorn/internal/ir/BaseNode.java b/src/jdk/nashorn/internal/ir/BaseNode.java index 26a28368..5e5bfb13 100644 --- a/src/jdk/nashorn/internal/ir/BaseNode.java +++ b/src/jdk/nashorn/internal/ir/BaseNode.java @@ -25,6 +25,10 @@ package jdk.nashorn.internal.ir; +import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS; +import jdk.nashorn.internal.codegen.ObjectClassGenerator; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.runtime.Source; /** @@ -33,12 +37,15 @@ import jdk.nashorn.internal.runtime.Source; * @see AccessNode * @see IndexNode */ -public abstract class BaseNode extends Node implements FunctionCall { +@Immutable +public abstract class BaseNode extends Node implements FunctionCall, TypeOverride<BaseNode> { /** Base Node. */ - protected Node base; + protected final Node base; - private boolean function; + private final boolean isFunction; + + private final boolean hasCallSiteType; /** * Constructor @@ -47,37 +54,28 @@ public abstract class BaseNode extends Node implements FunctionCall { * @param token token * @param finish finish * @param base base node + * @param isFunction is this a function + * @param hasCallSiteType does this access have a callsite type */ - public BaseNode(final Source source, final long token, final int finish, final Node base) { - super(source, token, finish); - this.base = base; - setStart(base.getStart()); + public BaseNode(final Source source, final long token, final int finish, final Node base, final boolean isFunction, final boolean hasCallSiteType) { + super(source, token, base.getStart(), finish); + this.base = base; + this.isFunction = isFunction; + this.hasCallSiteType = hasCallSiteType; } /** - * Copy constructor - * - * @param baseNode the base node - * @param cs a copy state + * Copy constructor for immutable nodes + * @param baseNode node to inherit from + * @param base base + * @param isFunction is this a function + * @param hasCallSiteType does this access have a callsite type */ - protected BaseNode(final BaseNode baseNode, final CopyState cs) { + protected BaseNode(final BaseNode baseNode, final Node base, final boolean isFunction, final boolean hasCallSiteType) { super(baseNode); - this.base = cs.existingOrCopy(baseNode.getBase()); - setStart(base.getStart()); - } - - @Override - public boolean equals(final Object other) { - if (!super.equals(other)) { - return false; - } - final BaseNode baseNode = (BaseNode)other; - return base.equals(baseNode.getBase()); - } - - @Override - public int hashCode() { - return base.hashCode(); + this.base = base; + this.isFunction = isFunction; + this.hasCallSiteType = hasCallSiteType; } /** @@ -88,25 +86,37 @@ public abstract class BaseNode extends Node implements FunctionCall { return base; } - /** - * Reset the base node for this access - * @param base new base node - */ - public void setBase(final Node base) { - this.base = base; - } - @Override public boolean isFunction() { - return function; + return isFunction; } /** * Mark this node as being the callee operand of a {@link CallNode}. * @return a base node identical to this one in all aspects except with its function flag set. */ - public BaseNode setIsFunction() { - function = true; - return this; + public abstract BaseNode setIsFunction(); + + @Override + public boolean canHaveCallSiteType() { + return true; //carried by the symbol and always the same nodetype==symboltype + } + + /** + * Does the access have a call site type override? + * @return true if overridden + */ + protected boolean hasCallSiteType() { + return hasCallSiteType; + } + + /** + * Debug type change + * @param type new type + */ + protected final void logTypeChange(final Type type) { + if (DEBUG_FIELDS && !Type.areEquivalent(getSymbol().getSymbolType(), type)) { + ObjectClassGenerator.LOG.info(getClass().getName(), " ", this, " => ", type, " instead of ", getType()); + } } } diff --git a/src/jdk/nashorn/internal/ir/BinaryNode.java b/src/jdk/nashorn/internal/ir/BinaryNode.java index 42c7a6cc..28df8edf 100644 --- a/src/jdk/nashorn/internal/ir/BinaryNode.java +++ b/src/jdk/nashorn/internal/ir/BinaryNode.java @@ -26,6 +26,7 @@ package jdk.nashorn.internal.ir; 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.TokenType; import jdk.nashorn.internal.runtime.Source; @@ -33,9 +34,12 @@ import jdk.nashorn.internal.runtime.Source; /** * BinaryNode nodes represent two operand operations. */ -public class BinaryNode extends UnaryNode { +@Immutable +public final class BinaryNode extends Node implements Assignment<Node> { /** Left hand side argument. */ - private Node lhs; + private final Node lhs; + + private final Node rhs; /** * Constructor @@ -46,28 +50,15 @@ public class BinaryNode extends UnaryNode { * @param rhs right hand side */ public BinaryNode(final Source source, final long token, final Node lhs, final Node rhs) { - super(source, token, rhs); - - start = lhs.getStart(); - finish = rhs.getFinish(); - - this.lhs = lhs; - } - - /** - * Copy constructor - * - * @param binaryNode the binary node - * @param cs copy state - */ - protected BinaryNode(final BinaryNode binaryNode, final CopyState cs) { - super(binaryNode, cs); - lhs = cs.existingOrCopy(binaryNode.lhs); + super(source, token, lhs.getStart(), rhs.getFinish()); + this.lhs = lhs; + this.rhs = rhs; } - @Override - protected Node copy(final CopyState cs) { - return new BinaryNode(this, cs); + private BinaryNode(final BinaryNode binaryNode, final Node lhs, final Node rhs) { + super(binaryNode); + this.lhs = lhs; + this.rhs = rhs; } /** @@ -149,28 +140,14 @@ public class BinaryNode extends UnaryNode { return rhs(); } - @Override - public boolean equals(final Object other) { - if (!super.equals(other)) { - return false; - } - return lhs.equals(((BinaryNode)other).lhs()); - } - - @Override - public int hashCode() { - return super.hashCode() ^ lhs().hashCode(); - } - /** * Assist in IR navigation. * @param visitor IR navigating visitor. */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterBinaryNode(this) != null) { - // TODO: good cause for a separate visitMembers: we could delegate to UnaryNode.visitMembers - return visitor.leaveBinaryNode((BinaryNode)setLHS(lhs.accept(visitor)).setRHS(rhs().accept(visitor))); + if (visitor.enterBinaryNode(this)) { + return visitor.leaveBinaryNode(setLHS(lhs.accept(visitor)).setRHS(rhs.accept(visitor))); } return this; @@ -231,14 +208,35 @@ public class BinaryNode extends UnaryNode { } /** + * Get the right hand side expression for this node + * @return the left hand side expression + */ + public Node rhs() { + return rhs; + } + + /** * Set the left hand side expression for this node * @param lhs new left hand side expression * @return a node equivalent to this one except for the requested change. */ public BinaryNode setLHS(final Node lhs) { - if(this.lhs == lhs) return this; - final BinaryNode n = (BinaryNode)clone(); - n.lhs = lhs; - return n; + if (this.lhs == lhs) { + return this; + } + return new BinaryNode(this, lhs, rhs); } + + /** + * Set the right hand side expression for this node + * @param rhs new left hand side expression + * @return a node equivalent to this one except for the requested change. + */ + public BinaryNode setRHS(final Node rhs) { + if (this.rhs == rhs) { + return this; + } + return new BinaryNode(this, lhs, rhs); + } + } diff --git a/src/jdk/nashorn/internal/ir/Block.java b/src/jdk/nashorn/internal/ir/Block.java index 6f138f85..48b9ed69 100644 --- a/src/jdk/nashorn/internal/ir/Block.java +++ b/src/jdk/nashorn/internal/ir/Block.java @@ -27,14 +27,15 @@ package jdk.nashorn.internal.ir; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; -import java.util.ListIterator; -import jdk.nashorn.internal.codegen.Frame; +import java.util.Map; import jdk.nashorn.internal.codegen.Label; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; @@ -42,97 +43,79 @@ import jdk.nashorn.internal.runtime.Source; * IR representation for a list of statements and functions. All provides the * basis for script body. */ -public class Block extends Node { +@Immutable +public class Block extends BreakableNode implements Flags<Block> { /** List of statements */ - protected List<Node> statements; + protected final List<Node> statements; - /** Symbol table. */ - protected final HashMap<String, Symbol> symbols; - - /** Variable frame. */ - protected Frame frame; + /** Symbol table - keys must be returned in the order they were put in. */ + protected final Map<String, Symbol> symbols; /** Entry label. */ protected final Label entryLabel; - /** Break label. */ - protected final Label breakLabel; - /** Does the block/function need a new scope? */ - protected boolean needsScope; + protected final int flags; + + /** Flag indicating that this block needs scope */ + public static final int NEEDS_SCOPE = 1 << 0; /** - * Constructor - * - * @param source source code - * @param token token - * @param finish finish + * Flag indicating whether this block needs + * self symbol assignment at the start. This is used only for + * blocks that are the bodies of function nodes who refer to themselves + * by name. It causes codegen to insert a var [fn_name] = __callee__ + * at the start of the body */ - public Block(final Source source, final long token, final int finish) { - super(source, token, finish); + public static final int NEEDS_SELF_SYMBOL = 1 << 1; - this.statements = new ArrayList<>(); - this.symbols = new HashMap<>(); - this.entryLabel = new Label("block_entry"); - this.breakLabel = new Label("block_break"); - } + /** + * Is this block tagged as terminal based on its contents + * (usually the last statement) + */ + public static final int IS_TERMINAL = 1 << 2; /** - * Internal copy constructor + * Constructor * - * @param block the source block - * @param cs the copy state + * @param source source code + * @param token token + * @param finish finish + * @param statements statements */ - protected Block(final Block block, final CopyState cs) { - super(block); + public Block(final Source source, final long token, final int finish, final Node... statements) { + super(source, token, finish, new Label("block_break")); - this.statements = new ArrayList<>(); - for (final Node statement : block.getStatements()) { - statements.add(cs.existingOrCopy(statement)); - } - this.symbols = new HashMap<>(); - this.frame = block.frame == null ? null : block.frame.copy(); - this.entryLabel = new Label(block.entryLabel); - this.breakLabel = new Label(block.breakLabel); - - assert block.symbols.isEmpty() : "must not clone with symbols"; - } - - @Override - protected Node copy(final CopyState cs) { - return new Block(this, cs); + this.statements = Arrays.asList(statements); + this.symbols = new LinkedHashMap<>(); + this.entryLabel = new Label("block_entry"); + this.flags = 0; } /** - * Add a new statement to the statement list. + * Constructor * - * @param statement Statement node to add. + * @param source source code + * @param token token + * @param finish finish + * @param statements statements */ - public void addStatement(final Node statement) { - if (statement != null) { - statements.add(statement); - if (getFinish() < statement.getFinish()) { - setFinish(statement.getFinish()); - } - } + public Block(final Source source, final long token, final int finish, final List<Node> statements) { + this(source, token, finish, statements.toArray(new Node[statements.size()])); } - /** - * Prepend statements to the statement list - * - * @param prepended statement to add - */ - public void prependStatements(final List<Node> prepended) { - statements.addAll(0, prepended); + private Block(final Block block, final int finish, final List<Node> statements, final int flags) { + super(block); + this.statements = statements; + this.flags = flags; + this.symbols = block.symbols; //todo - symbols have no dependencies on any IR node and can as far as we understand it be shallow copied now + this.entryLabel = new Label(block.entryLabel); + this.finish = finish; } - /** - * Add a list of statements to the statement list. - * - * @param statementList Statement nodes to add. - */ - public void addStatements(final List<Node> statementList) { - statements.addAll(statementList); + @Override + public Node ensureUniqueLabels(final LexicalContext lc) { + return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags)); } /** @@ -142,19 +125,9 @@ public class Block extends Node { * @return new or same node */ @Override - public Node accept(final NodeVisitor visitor) { - final Block saveBlock = visitor.getCurrentBlock(); - visitor.setCurrentBlock(this); - - try { - // Ignore parent to avoid recursion. - - if (visitor.enterBlock(this) != null) { - visitStatements(visitor); - return visitor.leaveBlock(this); - } - } finally { - visitor.setCurrentBlock(saveBlock); + public Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterBlock(this)) { + return visitor.leaveBlock(setStatements(lc, Node.accept(visitor, Node.class, statements))); } return this; @@ -222,11 +195,18 @@ public class Block extends Node { } /** - * Get the break label for this block - * @return the break label + * Tag block as terminal or non terminal + * @param lc lexical context + * @param isTerminal is block terminal + * @return same block, or new if flag changed */ - public Label getBreakLabel() { - return breakLabel; + public Block setIsTerminal(final LexicalContext lc, final boolean isTerminal) { + return isTerminal ? setFlag(lc, IS_TERMINAL) : clearFlag(lc, IS_TERMINAL); + } + + @Override + public boolean isTerminal() { + return getFlag(IS_TERMINAL); } /** @@ -238,23 +218,6 @@ public class Block extends Node { } /** - * Get the frame for this block - * @return the frame - */ - public Frame getFrame() { - return frame; - } - - /** - * Reset the frame for this block - * - * @param frame the new frame - */ - public void setFrame(final Frame frame) { - this.frame = frame; - } - - /** * Get the list of statements in this block * * @return a list of statements @@ -264,21 +227,21 @@ public class Block extends Node { } /** - * Applies the specified visitor to all statements in the block. - * @param visitor the visitor. - */ - public void visitStatements(NodeVisitor visitor) { - for (ListIterator<Node> stmts = statements.listIterator(); stmts.hasNext();) { - stmts.set(stmts.next().accept(visitor)); - } - } - /** * Reset the statement list for this block * - * @param statements new statement list + * @param lc lexical context + * @param statements new statement list + * @return new block if statements changed, identity of statements == block.statements */ - public void setStatements(final List<Node> statements) { - this.statements = statements; + public Block setStatements(final LexicalContext lc, final List<Node> statements) { + if (this.statements == statements) { + return this; + } + int lastFinish = 0; + if (!statements.isEmpty()) { + lastFinish = statements.get(statements.size() - 1).getFinish(); + } + return Node.replaceInLexicalContext(lc, this, new Block(this, Math.max(finish, lastFinish), statements, flags)); } /** @@ -297,39 +260,65 @@ public class Block extends Node { * @return true if this function needs a scope */ public boolean needsScope() { - return needsScope; + return (flags & NEEDS_SCOPE) == NEEDS_SCOPE; } - /** - * Set the needs scope flag. - */ - public void setNeedsScope() { - needsScope = true; + @Override + public Block setFlags(final LexicalContext lc, int flags) { + if (this.flags == flags) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags)); + } + + @Override + public Block clearFlag(final LexicalContext lc, int flag) { + return setFlags(lc, flags & ~flag); + } + + @Override + public Block setFlag(final LexicalContext lc, int flag) { + return setFlags(lc, flags | flag); + } + + @Override + public boolean getFlag(final int flag) { + return (flags & flag) == flag; } /** - * Marks this block as using a specified scoped symbol. The block and its parent blocks up to but not - * including the block defining the symbol will be marked as needing parent scope. The block defining the symbol - * will be marked as one that needs to have its own scope. - * @param symbol the symbol being used. - * @param ancestors the iterator over block's containing lexical context + * Set the needs scope flag. + * @param lc lexicalContext + * @return new block if state changed, otherwise this */ - public void setUsesScopeSymbol(final Symbol symbol, Iterator<Block> ancestors) { - if(symbol.getBlock() == this) { - setNeedsScope(); - } else { - setUsesParentScopeSymbol(symbol, ancestors); + public Block setNeedsScope(final LexicalContext lc) { + if (needsScope()) { + return this; } + + return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags | NEEDS_SCOPE)); } /** - * Invoked when this block uses a scope symbol defined in one of its ancestors. - * @param symbol the scope symbol being used - * @param ancestors iterator over ancestor blocks + * Computationally determine the next slot for this block, + * indexed from 0. Use this as a relative base when computing + * frames + * @return next slot */ - void setUsesParentScopeSymbol(final Symbol symbol, Iterator<Block> ancestors) { - if(ancestors.hasNext()) { - ancestors.next().setUsesScopeSymbol(symbol, ancestors); + public int nextSlot() { + final Iterator<Symbol> iter = symbolIterator(); + int next = 0; + while (iter.hasNext()) { + final Symbol symbol = iter.next(); + if (symbol.hasSlot()) { + next += symbol.slotCount(); } + } + return next; + } + + @Override + protected boolean isBreakableWithoutLabel() { + return false; } } diff --git a/src/jdk/nashorn/internal/ir/BlockLexicalContext.java b/src/jdk/nashorn/internal/ir/BlockLexicalContext.java new file mode 100644 index 00000000..38186f56 --- /dev/null +++ b/src/jdk/nashorn/internal/ir/BlockLexicalContext.java @@ -0,0 +1,120 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.ir; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.ListIterator; + +/** + * This is a subclass of lexical context used for filling + * blocks (and function nodes) with statements. When popping + * a block from the lexical context, any statements that have + * been generated in it are commited to the block. This saves + * unnecessary object mutations and lexical context replacement + */ +public class BlockLexicalContext extends LexicalContext { + /** statement stack, each block on the lexical context maintains one of these, which is + * committed to the block on pop */ + private Deque<List<Node>> sstack = new ArrayDeque<>(); + + /** Last non debug statement emitted in this context */ + protected Node lastStatement; + + @Override + public <T extends LexicalContextNode> T push(final T node) { + T pushed = super.push(node); + if (node instanceof Block) { + sstack.push(new ArrayList<Node>()); + } + return pushed; + } + + /** + * Get the statement list from the stack, possibly filtered + * @return statement list + */ + protected List<Node> popStatements() { + return sstack.pop(); + } + + @Override + @SuppressWarnings("unchecked") + public <T extends LexicalContextNode> T pop(final T node) { + T expected = node; + if (node instanceof Block) { + final List<Node> newStatements = popStatements(); + expected = (T)((Block)node).setStatements(this, newStatements); + if (!sstack.isEmpty()) { + lastStatement = lastStatement(sstack.peek()); + } + } + return super.pop(expected); + } + + /** + * Append a statement to the block being generated + * @param statement statement to add + */ + public void appendStatement(final Node statement) { + assert statement != null; + sstack.peek().add(statement); + if (!statement.isDebug()) { + lastStatement = statement; + } + } + + /** + * Prepend a statement to the block being generated + * @param statement statement to prepend + * @return the prepended statement + */ + public Node prependStatement(final Node statement) { + assert statement != null; + sstack.peek().add(0, statement); + return statement; + } + + /** + * Get the last (non debug) statement that was emitted into a block + * @return the last statement emitted + */ + public Node getLastStatement() { + return lastStatement; + } + + private static Node lastStatement(final List<Node> statements) { + for (final ListIterator<Node> iter = statements.listIterator(statements.size()); iter.hasPrevious(); ) { + final Node node = iter.previous(); + if (!node.isDebug()) { + return node; + } + } + return null; + } +} diff --git a/src/jdk/nashorn/internal/ir/BreakNode.java b/src/jdk/nashorn/internal/ir/BreakNode.java index 7ad0dc6d..f0966b46 100644 --- a/src/jdk/nashorn/internal/ir/BreakNode.java +++ b/src/jdk/nashorn/internal/ir/BreakNode.java @@ -25,37 +25,34 @@ package jdk.nashorn.internal.ir; -import jdk.nashorn.internal.codegen.Label; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for {@code break} statements. */ -public class BreakNode extends LabeledNode { +@Immutable +public final class BreakNode extends Node { - /** + private final IdentNode label; + + /** * Constructor * - * @param source source code - * @param token token - * @param finish finish - * @param labelNode break label - * @param targetNode node to break to - * @param tryChain surrounding try chain + * @param source source code + * @param token token + * @param finish finish + * @param label label for break or null if none */ - public BreakNode(final Source source, final long token, final int finish, final LabelNode labelNode, final Node targetNode, final TryNode tryChain) { - super(source, token, finish, labelNode, targetNode, tryChain); - setHasGoto(); - } - - private BreakNode(final BreakNode breakNode, final CopyState cs) { - super(breakNode, cs); + public BreakNode(final Source source, final long token, final int finish, final IdentNode label) { + super(source, token, finish); + this.label = label; } @Override - protected Node copy(final CopyState cs) { - return new BreakNode(this, cs); + public boolean hasGoto() { + return true; } /** @@ -64,7 +61,7 @@ public class BreakNode extends LabeledNode { */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterBreakNode(this) != null) { + if (visitor.enterBreakNode(this)) { return visitor.leaveBreakNode(this); } @@ -72,26 +69,20 @@ public class BreakNode extends LabeledNode { } /** - * Return the target label of this break node. - * @return the target label. + * Get the label for this break node + * @return label, or null if none */ - public Label getTargetLabel() { - if (targetNode instanceof BreakableNode) { - return ((BreakableNode)targetNode).getBreakLabel(); - } else if (targetNode instanceof Block) { - return ((Block)targetNode).getBreakLabel(); - } - - throw new AssertionError("Invalid break target " + targetNode.getClass()); + public IdentNode getLabel() { + return label; } @Override public void toString(final StringBuilder sb) { sb.append("break"); - if (labelNode != null) { + if (label != null) { sb.append(' '); - labelNode.getLabel().toString(sb); + label.toString(sb); } } } diff --git a/src/jdk/nashorn/internal/ir/BreakableNode.java b/src/jdk/nashorn/internal/ir/BreakableNode.java index d15b7564..3662bfa7 100644 --- a/src/jdk/nashorn/internal/ir/BreakableNode.java +++ b/src/jdk/nashorn/internal/ir/BreakableNode.java @@ -25,27 +25,34 @@ package jdk.nashorn.internal.ir; +import java.util.Arrays; +import java.util.List; + import jdk.nashorn.internal.codegen.Label; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.runtime.Source; /** * This class represents a node from which control flow can execute * a {@code break} statement */ -public abstract class BreakableNode extends Node { +@Immutable +public abstract class BreakableNode extends LexicalContextNode { /** break label. */ - protected Label breakLabel; + protected final Label breakLabel; /** * Constructor * - * @param source source code - * @param token token - * @param finish finish + * @param source source code + * @param token token + * @param finish finish + * @param breakLabel break label */ - public BreakableNode(final Source source, final long token, final int finish) { + protected BreakableNode(final Source source, final long token, final int finish, final Label breakLabel) { super(source, token, finish); + this.breakLabel = breakLabel; } /** @@ -55,6 +62,19 @@ public abstract class BreakableNode extends Node { */ protected BreakableNode(final BreakableNode breakableNode) { super(breakableNode); + this.breakLabel = new Label(breakableNode.getBreakLabel()); + } + + @Override + public abstract Node ensureUniqueLabels(final LexicalContext lc); + + /** + * Check whether this can be broken out from without using a label, + * e.g. everything but Blocks, basically + * @return true if breakable without label + */ + protected boolean isBreakableWithoutLabel() { + return true; } /** @@ -65,4 +85,14 @@ public abstract class BreakableNode extends Node { return breakLabel; } + /** + * Return the labels associated with this node. Breakable nodes that + * aren't LoopNodes only have a break label -> the location immediately + * afterwards the node in code + * @return list of labels representing locations around this node + */ + public List<Label> getLabels() { + return Arrays.asList(breakLabel); + } + } diff --git a/src/jdk/nashorn/internal/ir/CallNode.java b/src/jdk/nashorn/internal/ir/CallNode.java index 3410709c..9a730aa8 100644 --- a/src/jdk/nashorn/internal/ir/CallNode.java +++ b/src/jdk/nashorn/internal/ir/CallNode.java @@ -25,49 +25,48 @@ package jdk.nashorn.internal.ir; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.annotations.Ignore; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for a function call. - * */ -public class CallNode extends Node implements TypeOverride<CallNode> { +@Immutable +public final class CallNode extends LexicalContextNode implements TypeOverride<CallNode> { - private Type type; + private final Type type; /** Function identifier or function body. */ - private Node function; + private final Node function; /** Call arguments. */ - private List<Node> args; + private final List<Node> args; - /** flag - is new expression */ - private boolean isNew; + /** Is this a "new" operation */ + public static final int IS_NEW = 0x1; - /** flag - is in with block */ - private boolean inWithBlock; + private final int flags; /** * Arguments to be passed to builtin {@code eval} function */ public static class EvalArgs { /** evaluated code */ - private Node code; + private final Node code; /** 'this' passed to evaluated code */ - private IdentNode evalThis; + private final IdentNode evalThis; /** location string for the eval call */ - final private String location; + private final String location; /** is this call from a strict context? */ - final private boolean strictMode; + private final boolean strictMode; /** * Constructor @@ -92,12 +91,11 @@ public class CallNode extends Node implements TypeOverride<CallNode> { return code; } - /** - * Set the code that is to be eval.ed by this eval function - * @param code the code as an AST node - */ - public void setCode(final Node code) { - this.code = code; + private EvalArgs setCode(final Node code) { + if (this.code == code) { + return this; + } + return new EvalArgs(code, evalThis, location, strictMode); } /** @@ -108,12 +106,11 @@ public class CallNode extends Node implements TypeOverride<CallNode> { return this.evalThis; } - /** - * Set the {@code this} symbol used to invoke this eval call - * @param evalThis the {@code this} symbol - */ - public void setThis(final IdentNode evalThis) { - this.evalThis = evalThis; + private EvalArgs setThis(final IdentNode evalThis) { + if (this.evalThis == evalThis) { + return this; + } + return new EvalArgs(code, evalThis, location, strictMode); } /** @@ -135,7 +132,7 @@ public class CallNode extends Node implements TypeOverride<CallNode> { /** arguments for 'eval' call. Non-null only if this call node is 'eval' */ @Ignore - private EvalArgs evalArgs; + private final EvalArgs evalArgs; /** * Constructors @@ -149,28 +146,22 @@ public class CallNode extends Node implements TypeOverride<CallNode> { public CallNode(final Source source, final long token, final int finish, final Node function, final List<Node> args) { super(source, token, finish); - setStart(function.getStart()); - - this.function = function; - this.args = args; + this.function = function; + this.args = args; + this.flags = 0; + this.type = null; + this.evalArgs = null; } - private CallNode(final CallNode callNode, final CopyState cs) { + private CallNode(final CallNode callNode, final Node function, final List<Node> args, final int flags, final Type type, final EvalArgs evalArgs) { super(callNode); - - final List<Node> newArgs = new ArrayList<>(); - - for (final Node arg : callNode.args) { - newArgs.add(cs.existingOrCopy(arg)); - } - - this.function = cs.existingOrCopy(callNode.function); //TODO existing or same? - this.args = newArgs; - this.isNew = callNode.isNew; - this.inWithBlock = callNode.inWithBlock; + this.function = function; + this.args = args; + this.flags = flags; + this.type = type; + this.evalArgs = evalArgs; } - @Override public Type getType() { if (hasCallSiteType()) { @@ -181,8 +172,10 @@ public class CallNode extends Node implements TypeOverride<CallNode> { @Override public CallNode setType(final Type type) { - this.type = type; - return this; + if (this.type == type) { + return this; + } + return new CallNode(this, function, args, flags, type, evalArgs); } private boolean hasCallSiteType() { @@ -194,11 +187,6 @@ public class CallNode extends Node implements TypeOverride<CallNode> { return true; } - @Override - protected Node copy(final CopyState cs) { - return new CallNode(this, cs); - } - /** * Assist in IR navigation. * @@ -207,15 +195,22 @@ public class CallNode extends Node implements TypeOverride<CallNode> { * @return node or replacement */ @Override - public Node accept(final NodeVisitor visitor) { - if (visitor.enterCallNode(this) != null) { - function = function.accept(visitor); - - for (int i = 0, count = args.size(); i < count; i++) { - args.set(i, args.get(i).accept(visitor)); + public Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterCallNode(this)) { + final CallNode newCallNode = (CallNode)visitor.leaveCallNode( + setFunction(function.accept(visitor)). + setArgs(Node.accept(visitor, Node.class, args)). + setFlags(flags). + setType(type). + setEvalArgs(evalArgs == null ? + null : + evalArgs.setCode(evalArgs.getCode().accept(visitor)). + setThis((IdentNode)evalArgs.getThis().accept(visitor)))); + // Theoretically, we'd need to instead pass lc to every setter and do a replacement on each. In practice, + // setType from TypeOverride can't accept a lc, and we don't necessarily want to go there now. + if(this != newCallNode) { + return Node.replaceInLexicalContext(lc, this, newCallNode); } - - return visitor.leaveCallNode(this); } return this; @@ -226,7 +221,7 @@ public class CallNode extends Node implements TypeOverride<CallNode> { if (hasCallSiteType()) { sb.append('{'); final String desc = getType().getDescriptor(); - sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : getType().getDescriptor()); + sb.append(desc.charAt(desc.length() - 1) == ';' ? 'O' : getType().getDescriptor()); sb.append('}'); } @@ -261,8 +256,11 @@ public class CallNode extends Node implements TypeOverride<CallNode> { * Reset the arguments for the call * @param args new arguments list */ - public void setArgs(final List<Node> args) { - this.args = args; + private CallNode setArgs(final List<Node> args) { + if (this.args == args) { + return this; + } + return new CallNode(this, function, args, flags, type, evalArgs); } /** @@ -278,9 +276,13 @@ public class CallNode extends Node implements TypeOverride<CallNode> { * {@code eval} * * @param evalArgs eval args + * @return same node or new one on state change */ - public void setEvalArgs(final EvalArgs evalArgs) { - this.evalArgs = evalArgs; + public CallNode setEvalArgs(final EvalArgs evalArgs) { + if (this.evalArgs == evalArgs) { + return this; + } + return new CallNode(this, function, args, flags, type, evalArgs); } /** @@ -301,10 +303,14 @@ public class CallNode extends Node implements TypeOverride<CallNode> { /** * Reset the function expression that this call invokes - * @param node the function + * @param function the function + * @return same node or new one on state change */ - public void setFunction(final Node node) { - function = node; + public CallNode setFunction(final Node function) { + if (this.function == function) { + return this; + } + return new CallNode(this, function, args, flags, type, evalArgs); } /** @@ -312,28 +318,21 @@ public class CallNode extends Node implements TypeOverride<CallNode> { * @return true if this a new operation */ public boolean isNew() { - return isNew; + return (flags & IS_NEW) == IS_NEW; } /** * Flag this call as a new operation + * @return same node or new one on state change */ - public void setIsNew() { - this.isNew = true; + public CallNode setIsNew() { + return setFlags(IS_NEW); } - /** - * Check if this call is inside a {@code with} block - * @return true if the call is inside a {@code with} block - */ - public boolean inWithBlock() { - return inWithBlock; - } - - /** - * Flag this call to be inside a {@code with} block - */ - public void setInWithBlock() { - this.inWithBlock = true; + private CallNode setFlags(final int flags) { + if (this.flags == flags) { + return this; + } + return new CallNode(this, function, args, flags, type, evalArgs); } } diff --git a/src/jdk/nashorn/internal/ir/CaseNode.java b/src/jdk/nashorn/internal/ir/CaseNode.java index 61b89217..237536cc 100644 --- a/src/jdk/nashorn/internal/ir/CaseNode.java +++ b/src/jdk/nashorn/internal/ir/CaseNode.java @@ -26,19 +26,21 @@ package jdk.nashorn.internal.ir; import jdk.nashorn.internal.codegen.Label; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation of CASE clause. - * + * Case nodes are not BreakableNodes, but the SwitchNode is */ -public class CaseNode extends BreakableNode { +@Immutable +public final class CaseNode extends Node { /** Test expression. */ - private Node test; + private final Node test; /** Statements. */ - private Block body; + private final Block body; /** Case entry label. */ private final Label entry; @@ -60,17 +62,17 @@ public class CaseNode extends BreakableNode { this.entry = new Label("entry"); } - private CaseNode(final CaseNode caseNode, final CopyState cs) { + CaseNode(final CaseNode caseNode, final Node test, final Block body) { super(caseNode); - this.test = cs.existingOrCopy(caseNode.test); - this.body = (Block)cs.existingOrCopy(caseNode.body); + this.test = test; + this.body = body; this.entry = new Label(caseNode.entry); } @Override - protected Node copy(final CopyState cs) { - return new CaseNode(this, cs); + public boolean isTerminal() { + return body.isTerminal(); } /** @@ -79,15 +81,11 @@ public class CaseNode extends BreakableNode { */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterCaseNode(this) != null) { - if (test != null) { - test = test.accept(visitor); - } - if (body != null) { - body = (Block)body.accept(visitor); - } - - return visitor.leaveCaseNode(this); + if (visitor.enterCaseNode(this)) { + final Node newTest = test == null ? null : test.accept(visitor); + final Block newBody = body == null ? null : (Block)body.accept(visitor); + + return visitor.leaveCaseNode(setTest(newTest).setBody(newBody)); } return this; @@ -131,8 +129,19 @@ public class CaseNode extends BreakableNode { /** * Reset the test expression for this case node * @param test new test expression + * @return new or same CaseNode */ - public void setTest(final Node test) { - this.test = test; + public CaseNode setTest(final Node test) { + if (this.test == test) { + return this; + } + return new CaseNode(this, test, body); + } + + private CaseNode setBody(final Block body) { + if (this.body == body) { + return this; + } + return new CaseNode(this, test, body); } } diff --git a/src/jdk/nashorn/internal/ir/CatchNode.java b/src/jdk/nashorn/internal/ir/CatchNode.java index 005ffa8e..5e1b4111 100644 --- a/src/jdk/nashorn/internal/ir/CatchNode.java +++ b/src/jdk/nashorn/internal/ir/CatchNode.java @@ -25,26 +25,23 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation of a catch clause. - * */ -public class CatchNode extends Node { +@Immutable +public final class CatchNode extends Node { /** Exception identifier. */ - private IdentNode exception; + private final IdentNode exception; /** Exception condition. */ - private Node exceptionCondition; + private final Node exceptionCondition; /** Catch body. */ - private Block body; - - /** Is rethrow - e.g. synthetic catch block for e.g. finallies, the parser case where - * there has to be at least on catch for syntactic validity */ - private boolean isSyntheticRethrow; + private final Block body; /** * Constructors @@ -64,18 +61,12 @@ public class CatchNode extends Node { this.body = body; } - private CatchNode(final CatchNode catchNode, final CopyState cs) { + private CatchNode(final CatchNode catchNode, final IdentNode exception, final Node exceptionCondition, final Block body) { super(catchNode); - this.exception = (IdentNode)cs.existingOrCopy(catchNode.exception); - this.exceptionCondition = cs.existingOrCopy(catchNode.exceptionCondition); - this.body = (Block)cs.existingOrCopy(catchNode.body); - this.isSyntheticRethrow = catchNode.isSyntheticRethrow; - } - - @Override - protected Node copy(final CopyState cs) { - return new CatchNode(this, cs); + this.exception = exception; + this.exceptionCondition = exceptionCondition; + this.body = body; } /** @@ -84,21 +75,22 @@ public class CatchNode extends Node { */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterCatchNode(this) != null) { - exception = (IdentNode)exception.accept(visitor); - - if (exceptionCondition != null) { - exceptionCondition = exceptionCondition.accept(visitor); - } - - body = (Block)body.accept(visitor); - return visitor.leaveCatchNode(this); + if (visitor.enterCatchNode(this)) { + return visitor.leaveCatchNode( + setException((IdentNode)exception.accept(visitor)). + setExceptionCondition(exceptionCondition == null ? null : exceptionCondition.accept(visitor)). + setBody((Block)body.accept(visitor))); } return this; } @Override + public boolean isTerminal() { + return body.isTerminal(); + } + + @Override public void toString(final StringBuilder sb) { sb.append(" catch ("); exception.toString(sb); @@ -111,23 +103,6 @@ public class CatchNode extends Node { } /** - * Check if this catch is a synthetic rethrow - * @return true if this is a synthetic rethrow - */ - public boolean isSyntheticRethrow() { - return isSyntheticRethrow; - } - - /** - * Flag this as deliberatly generated catch all that rethrows the - * caught exception. This is used for example for generating finally - * expressions - */ - public void setIsSyntheticRethrow() { - this.isSyntheticRethrow = true; - } - - /** * Get the identifier representing the exception thrown * @return the exception identifier */ @@ -146,9 +121,13 @@ public class CatchNode extends Node { /** * Reset the exception condition for this catch block * @param exceptionCondition the new exception condition + * @return new or same CatchNode */ - public void setExceptionCondition(final Node exceptionCondition) { - this.exceptionCondition = exceptionCondition; + public CatchNode setExceptionCondition(final Node exceptionCondition) { + if (this.exceptionCondition == exceptionCondition) { + return this; + } + return new CatchNode(this, exception, exceptionCondition, body); } /** @@ -158,4 +137,18 @@ public class CatchNode extends Node { public Block getBody() { return body; } + + private CatchNode setException(final IdentNode exception) { + if (this.exception == exception) { + return this; + } + return new CatchNode(this, exception, exceptionCondition, body); + } + + private CatchNode setBody(final Block body) { + if (this.body == body) { + return this; + } + return new CatchNode(this, exception, exceptionCondition, body); + } } diff --git a/src/jdk/nashorn/internal/ir/ContinueNode.java b/src/jdk/nashorn/internal/ir/ContinueNode.java index cbc7bff2..c8cc309d 100644 --- a/src/jdk/nashorn/internal/ir/ContinueNode.java +++ b/src/jdk/nashorn/internal/ir/ContinueNode.java @@ -25,43 +25,39 @@ package jdk.nashorn.internal.ir; -import jdk.nashorn.internal.codegen.Label; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for CONTINUE statements. - * */ -public class ContinueNode extends LabeledNode { +@Immutable +public class ContinueNode extends Node { + + private IdentNode label; /** * Constructor * - * @param source the source - * @param token token - * @param finish finish - * @param labelNode the continue label - * @param targetNode node to continue to - * @param tryChain surrounding try chain + * @param source source code + * @param token token + * @param finish finish + * @param label label for break or null if none */ - public ContinueNode(final Source source, final long token, final int finish, final LabelNode labelNode, final Node targetNode, final TryNode tryChain) { - super(source, token, finish, labelNode, targetNode, tryChain); - setHasGoto(); - } - - private ContinueNode(final ContinueNode continueNode, final CopyState cs) { - super(continueNode, cs); + public ContinueNode(final Source source, final long token, final int finish, final IdentNode label) { + super(source, token, finish); + this.label = label; } @Override - protected Node copy(final CopyState cs) { - return new ContinueNode(this, cs); + public boolean hasGoto() { + return true; } @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterContinueNode(this) != null) { + if (visitor.enterContinueNode(this)) { return visitor.leaveContinueNode(this); } @@ -69,21 +65,20 @@ public class ContinueNode extends LabeledNode { } /** - * Return the target label of this continue node. - * @return the target label. + * Get the label for this break node + * @return label, or null if none */ - public Label getTargetLabel() { - assert targetNode instanceof WhileNode : "continue target must be a while node"; - return ((WhileNode)targetNode).getContinueLabel(); + public IdentNode getLabel() { + return label; } @Override public void toString(final StringBuilder sb) { sb.append("continue"); - if (labelNode != null) { + if (label != null) { sb.append(' '); - labelNode.getLabel().toString(sb); + label.toString(sb); } } } diff --git a/src/jdk/nashorn/internal/ir/EmptyNode.java b/src/jdk/nashorn/internal/ir/EmptyNode.java index 15330a37..67516127 100644 --- a/src/jdk/nashorn/internal/ir/EmptyNode.java +++ b/src/jdk/nashorn/internal/ir/EmptyNode.java @@ -25,14 +25,15 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for an empty statement. - * */ -public class EmptyNode extends Node { +@Immutable +public final class EmptyNode extends Node { /** * Constructor @@ -57,7 +58,7 @@ public class EmptyNode extends Node { @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterEmptyNode(this) != null) { + if (visitor.enterEmptyNode(this)) { return visitor.leaveEmptyNode(this); } return this; diff --git a/src/jdk/nashorn/internal/ir/ExecuteNode.java b/src/jdk/nashorn/internal/ir/ExecuteNode.java index 8ae7d556..f6dd7d1b 100644 --- a/src/jdk/nashorn/internal/ir/ExecuteNode.java +++ b/src/jdk/nashorn/internal/ir/ExecuteNode.java @@ -25,6 +25,7 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; @@ -33,9 +34,10 @@ import jdk.nashorn.internal.runtime.Source; * node means "this code will be executed" and evaluating it results in * statements being added to the IR */ -public class ExecuteNode extends Node { +@Immutable +public final class ExecuteNode extends Node { /** Expression to execute. */ - private Node expression; + private final Node expression; /** * Constructor @@ -50,6 +52,11 @@ public class ExecuteNode extends Node { this.expression = expression; } + private ExecuteNode(final ExecuteNode executeNode, final Node expression) { + super(executeNode); + this.expression = expression; + } + /** * Constructor * @@ -60,34 +67,15 @@ public class ExecuteNode extends Node { this.expression = expression; } - private ExecuteNode(final ExecuteNode executeNode, final CopyState cs) { - super(executeNode); - this.expression = cs.existingOrCopy(executeNode.expression); - } - - @Override - protected Node copy(final CopyState cs) { - return new ExecuteNode(this, cs); - } - - @Override - public boolean equals(final Object other) { - if (!super.equals(other)) { - return false; - } - return expression.equals(((ExecuteNode)other).getExpression()); - } - @Override - public int hashCode() { - return super.hashCode() ^ expression.hashCode(); + public boolean isTerminal() { + return expression.isTerminal(); } @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterExecuteNode(this) != null) { - setExpression(expression.accept(visitor)); - return visitor.leaveExecuteNode(this); + if (visitor.enterExecuteNode(this)) { + return visitor.leaveExecuteNode(setExpression(expression.accept(visitor))); } return this; @@ -109,8 +97,12 @@ public class ExecuteNode extends Node { /** * Reset the expression to be executed * @param expression the expression + * @return new or same execute node */ - public void setExpression(final Node expression) { - this.expression = expression; + public ExecuteNode setExpression(final Node expression) { + if (this.expression == expression) { + return this; + } + return new ExecuteNode(this, expression); } } diff --git a/src/jdk/nashorn/internal/ir/Flags.java b/src/jdk/nashorn/internal/ir/Flags.java new file mode 100644 index 00000000..94a21098 --- /dev/null +++ b/src/jdk/nashorn/internal/ir/Flags.java @@ -0,0 +1,69 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.ir; + +/** + * Interface implemented by all nodes that have flags in + * a lexical context. This is needed as we sometimes have to save + * the setting of flags in the lexical context until a block + * is completely finished and its final version (after multiple + * copy on writes) is placed in the lexical context + * + * @param <T> lexical context node that can have flags set during code generation + */ +public interface Flags<T extends LexicalContextNode> { + + /** + * Check if a flag is set in a lexical context node + * @param flag flag to check + * @return flags + */ + public boolean getFlag(int flag); + + /** + * Clear a flag of a LexicalContextNode + * @param lc lexical context + * @param flag flag to clear + * @return the new LexicalContext node if flags were changed, same otherwise + */ + public T clearFlag(final LexicalContext lc, int flag); + + /** + * Set a flag of a LexicalContextNode + * @param lc lexical context + * @param flag flag to set + * @return the new LexicalContext node if flags were changed, same otherwise + */ + public T setFlag(final LexicalContext lc, int flag); + + /** + * Set all flags of a LexicalContextNode, overwriting previous flags + * @param lc lexical context + * @param flags new flags value + * @return the new LexicalContext node if flags were changed, same otherwise + */ + public T setFlags(final LexicalContext lc, int flags); +} diff --git a/src/jdk/nashorn/internal/ir/ForNode.java b/src/jdk/nashorn/internal/ir/ForNode.java index e55054dd..057b8464 100644 --- a/src/jdk/nashorn/internal/ir/ForNode.java +++ b/src/jdk/nashorn/internal/ir/ForNode.java @@ -25,73 +25,75 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representing a FOR statement. - * */ -public class ForNode extends WhileNode { +@Immutable +public final class ForNode extends LoopNode { /** Initialize expression. */ - private Node init; + private final Node init; /** Test expression. */ - private Node modify; + private final Node modify; /** Iterator symbol. */ private Symbol iterator; - /** is for in */ - private boolean isForIn; + /** Is this a normal for loop? */ + public static final int IS_FOR = 1 << 0; + + /** Is this a normal for in loop? */ + public static final int IS_FOR_IN = 1 << 1; + + /** Is this a normal for each in loop? */ + public static final int IS_FOR_EACH = 1 << 2; - /** is for each */ - private boolean isForEach; + private final int flags; /** * Constructor * - * @param source the source - * @param token token - * @param finish finish + * @param source the source + * @param token token + * @param finish finish + * @param init init + * @param test test + * @param body body + * @param modify modify + * @param flags flags */ - public ForNode(final Source source, final long token, final int finish) { - super(source, token, finish); + public ForNode(final Source source, final long token, final int finish, final Node init, final Node test, final Block body, final Node modify, final int flags) { + super(source, token, finish, test, body, false); + this.init = init; + this.modify = modify; + this.flags = flags; } - private ForNode(final ForNode forNode, final CopyState cs) { - super(forNode, cs); - - this.init = cs.existingOrCopy(forNode.init); - this.modify = cs.existingOrCopy(forNode.modify); - this.iterator = forNode.iterator; - this.isForIn = forNode.isForIn; - this.isForEach = forNode.isForEach; + private ForNode(final ForNode forNode, final Node init, final Node test, final Block body, final Node modify, final int flags, final boolean controlFlowEscapes) { + super(forNode, test, body, controlFlowEscapes); + this.init = init; + this.modify = modify; + this.flags = flags; + this.iterator = forNode.iterator; //TODO is this acceptable? symbols are never cloned, just copied as references } @Override - protected Node copy(final CopyState cs) { - return new ForNode(this, cs); + public Node ensureUniqueLabels(LexicalContext lc) { + return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes)); } @Override - public Node accept(final NodeVisitor visitor) { - if (visitor.enterForNode(this) != null) { - if (init != null) { - init = init.accept(visitor); - } - - if (test != null) { - test = test.accept(visitor); - } - - if (modify != null) { - modify = modify.accept(visitor); - } - - body = (Block)body.accept(visitor); - - return visitor.leaveForNode(this); + protected Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterForNode(this)) { + return visitor.leaveForNode( + setInit(lc, init == null ? null : init.accept(visitor)). + setTest(lc, test == null ? null : test.accept(visitor)). + setModify(lc, modify == null ? null : modify.accept(visitor)). + setBody(lc, (Block)body.accept(visitor))); } return this; @@ -122,6 +124,19 @@ public class ForNode extends WhileNode { sb.append(')'); } + @Override + public boolean hasGoto() { + return !isForIn() && test == null; + } + + @Override + public boolean mustEnter() { + if (isForIn()) { + return false; //may be an empty set to iterate over, then we skip the loop + } + return test == null; + } + /** * Get the initialization expression for this for loop * @return the initialization expression @@ -132,10 +147,15 @@ public class ForNode extends WhileNode { /** * Reset the initialization expression for this for loop + * @param lc lexical context * @param init new initialization expression + * @return new for node if changed or existing if not */ - public void setInit(final Node init) { - this.init = init; + public ForNode setInit(final LexicalContext lc, final Node init) { + if (this.init == init) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes)); } /** @@ -143,14 +163,16 @@ public class ForNode extends WhileNode { * @return true if this is a for in constructor */ public boolean isForIn() { - return isForIn; + return (flags & IS_FOR_IN) != 0; } /** * Flag this to be a for in construct + * @param lc lexical context + * @return new for node if changed or existing if not */ - public void setIsForIn() { - this.isForIn = true; + public ForNode setIsForIn(final LexicalContext lc) { + return setFlags(lc, flags | IS_FOR_IN); } /** @@ -159,14 +181,16 @@ public class ForNode extends WhileNode { * @return true if this is a for each construct */ public boolean isForEach() { - return isForEach; + return (flags & IS_FOR_EACH) != 0; } /** * Flag this to be a for each construct + * @param lc lexical context + * @return new for node if changed or existing if not */ - public void setIsForEach() { - this.isForEach = true; + public ForNode setIsForEach(final LexicalContext lc) { + return setFlags(lc, flags | IS_FOR_EACH); } /** @@ -195,10 +219,15 @@ public class ForNode extends WhileNode { /** * Reset the modification expression for this ForNode + * @param lc lexical context * @param modify new modification expression + * @return new for node if changed or existing if not */ - public void setModify(final Node modify) { - this.modify = modify; + public ForNode setModify(final LexicalContext lc, final Node modify) { + if (this.modify == modify) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes)); } @Override @@ -207,7 +236,39 @@ public class ForNode extends WhileNode { } @Override - public void setTest(final Node test) { - this.test = test; + public ForNode setTest(final LexicalContext lc, final Node test) { + if (this.test == test) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes)); } + + @Override + public Block getBody() { + return body; + } + + @Override + public ForNode setBody(final LexicalContext lc, final Block body) { + if (this.body == body) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes)); + } + + @Override + public ForNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes) { + if (this.controlFlowEscapes == controlFlowEscapes) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes)); + } + + private ForNode setFlags(final LexicalContext lc, final int flags) { + if (this.flags == flags) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes)); + } + } diff --git a/src/jdk/nashorn/internal/ir/FunctionNode.java b/src/jdk/nashorn/internal/ir/FunctionNode.java index 2b0e109b..60fe64ff 100644 --- a/src/jdk/nashorn/internal/ir/FunctionNode.java +++ b/src/jdk/nashorn/internal/ir/FunctionNode.java @@ -30,23 +30,19 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.TEMP_PREFIX; import static jdk.nashorn.internal.ir.Symbol.IS_CONSTANT; import static jdk.nashorn.internal.ir.Symbol.IS_TEMP; -import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; -import java.util.HashMap; -import java.util.Iterator; +import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Stack; +import java.util.Set; import jdk.nashorn.internal.codegen.CompileUnit; import jdk.nashorn.internal.codegen.Compiler; -import jdk.nashorn.internal.codegen.Frame; -import jdk.nashorn.internal.codegen.MethodEmitter; +import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.Namespace; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.annotations.Ignore; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; -import jdk.nashorn.internal.parser.Parser; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.UserAccessorProperty; @@ -55,9 +51,11 @@ import jdk.nashorn.internal.runtime.linker.LinkerCallSite; /** * IR representation for function (or script.) */ -public class FunctionNode extends Block { +@Immutable +public final class FunctionNode extends LexicalContextNode implements Flags<FunctionNode> { - private static final Type FUNCTION_TYPE = Type.typeFor(ScriptFunction.class); + /** Type used for all FunctionNodes */ + public static final Type FUNCTION_TYPE = Type.typeFor(ScriptFunction.class); /** Function kinds */ public enum Kind { @@ -90,134 +88,106 @@ public class FunctionNode extends Block { /** method has had its types finalized */ FINALIZED, /** method has been emitted to bytecode */ - EMITTED, - /** code installed in a class loader */ - INSTALLED + EMITTED } /** External function identifier. */ @Ignore - private IdentNode ident; + private final IdentNode ident; + + /** The body of the function node */ + private final Block body; /** Internal function name. */ - private String name; + private final String name; /** Compilation unit. */ - private CompileUnit compileUnit; - - /** Method emitter for current method. */ - private MethodEmitter method; + private final CompileUnit compileUnit; /** Function kind. */ - private Kind kind; + private final Kind kind; /** List of parameters. */ - private List<IdentNode> parameters; + private final List<IdentNode> parameters; /** First token of function. **/ - private long firstToken; + private final long firstToken; /** Last token of function. **/ - private long lastToken; + private final long lastToken; - /** Variable frames. */ - private Frame frames; + /** Declared symbols in this function node */ + @Ignore + private final Set<Symbol> declaredSymbols; /** Method's namespace. */ private final Namespace namespace; - /** Node representing current this. */ - @Ignore - private IdentNode thisNode; - - /** Node representing current scope. */ - @Ignore - private IdentNode scopeNode; - - /** Node representing return value. */ - @Ignore - private IdentNode resultNode; - - /** Node representing current arguments. */ - @Ignore - private IdentNode argumentsNode; - - /** Node representing callee */ - @Ignore - private IdentNode calleeNode; - - /** Node representing varargs */ - @Ignore - private IdentNode varArgsNode; - - /** Pending label list. */ - private final Stack<LabelNode> labelStack; - - /** Pending control list. */ - private final Stack<Node> controlStack; - - /** VarNode for this function statement */ - @Ignore //this is explicit code anyway and should not be traversed after lower - private VarNode funcVarNode; - - /** Line number for function declaration */ - @Ignore - private LineNumberNode funcVarLineNumberNode; - - /** Initializer var func = __callee__, where applicable */ - @Ignore - private Node selfSymbolInit; - /** Current compilation state */ @Ignore private final EnumSet<CompilationState> compilationState; - /** Type hints, e.g based on parameters at call site */ - private final Map<IdentNode, Type> specializedTypes; - /** Function flags. */ - private int flags; + private final int flags; /** Is anonymous function flag. */ - private static final int IS_ANONYMOUS = 1 << 0; + public static final int IS_ANONYMOUS = 1 << 0; + /** Is the function created in a function declaration (as opposed to a function expression) */ - private static final int IS_DECLARED = 1 << 1; + public static final int IS_DECLARED = 1 << 1; + /** is this a strict mode function? */ - private static final int IS_STRICT_MODE = 1 << 2; + public static final int IS_STRICT = 1 << 2; + /** Does the function use the "arguments" identifier ? */ - private static final int USES_ARGUMENTS = 1 << 3; - /** Are we lowered ? */ - private static final int IS_LOWERED = 1 << 4; + public static final int USES_ARGUMENTS = 1 << 3; + /** Has this node been split because it was too large? */ - private static final int IS_SPLIT = 1 << 5; - /** Does the function call eval? */ - private static final int HAS_EVAL = 1 << 6; - /** Does the function contain a with block ? */ - private static final int HAS_WITH = 1 << 7; - /** Does a descendant function contain a with or eval? */ - private static final int HAS_DESCENDANT_WITH_OR_EVAL = 1 << 8; - /** Does the function define "arguments" identifier as a parameter of nested function name? */ - private static final int DEFINES_ARGUMENTS = 1 << 9; - /** Does the function need a self symbol? */ - private static final int NEEDS_SELF_SYMBOL = 1 << 10; + public static final int IS_SPLIT = 1 << 4; + + /** Does the function call eval? If it does, then all variables in this function might be get/set by it and it can + * introduce new variables into this function's scope too.*/ + public static final int HAS_EVAL = 1 << 5; + + /** Does a nested function contain eval? If it does, then all variables in this function might be get/set by it. */ + public static final int HAS_NESTED_EVAL = 1 << 6; + + /** + * Flag this function as one that defines the identifier "arguments" as a function parameter or nested function + * name. This precludes it from needing to have an Arguments object defined as "arguments" local variable. Note that + * defining a local variable named "arguments" still requires construction of the Arguments object (see + * ECMAScript 5.1 Chapter 10.5). + * @see #needsArguments() + */ + public static final int DEFINES_ARGUMENTS = 1 << 8; + /** Does this function or any of its descendants use variables from an ancestor function's scope (incl. globals)? */ - private static final int USES_ANCESTOR_SCOPE = 1 << 11; + public static final int USES_ANCESTOR_SCOPE = 1 << 9; + /** Is this function lazily compiled? */ - private static final int IS_LAZY = 1 << 12; + public static final int IS_LAZY = 1 << 10; + /** Does this function have lazy, yet uncompiled children */ - private static final int HAS_LAZY_CHILDREN = 1 << 13; + public static final int HAS_LAZY_CHILDREN = 1 << 11; + /** Does this function have lazy, yet uncompiled children */ - private static final int IS_PROGRAM = 1 << 14; + public static final int IS_PROGRAM = 1 << 12; + + /** Does this function have nested declarations? */ + public static final int HAS_FUNCTION_DECLARATIONS = 1 << 13; + + /** Does this function or any nested functions contain an eval? */ + private static final int HAS_DEEP_EVAL = HAS_EVAL | HAS_NESTED_EVAL; - /** Does this function or any nested functions contain a with or an eval? */ - private static final int HAS_DEEP_WITH_OR_EVAL = HAS_EVAL | HAS_WITH | HAS_DESCENDANT_WITH_OR_EVAL; /** Does this function need to store all its variables in scope? */ - private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_WITH_OR_EVAL | IS_SPLIT | HAS_LAZY_CHILDREN; + private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_EVAL | IS_SPLIT | HAS_LAZY_CHILDREN; + /** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */ private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL; - /** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep with or eval. + + /** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep eval. * We also pessimistically need a parent scope if we have lazy children that have not yet been compiled */ - private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_WITH_OR_EVAL | HAS_LAZY_CHILDREN; + private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL | HAS_LAZY_CHILDREN; /** What is the return type of this function? */ private Type returnType = Type.UNKNOWN; @@ -225,107 +195,77 @@ public class FunctionNode extends Block { /** * Constructor * - * @param source the source - * @param token token - * @param finish finish - * @param namespace the namespace - * @param ident the identifier - * @param name the name of the function - */ - public FunctionNode(final Source source, final long token, final int finish, final Namespace namespace, final IdentNode ident, final String name) { + * @param source the source + * @param token token + * @param finish finish + * @param firstToken first token of the funtion node (including the function declaration) + * @param namespace the namespace + * @param ident the identifier + * @param name the name of the function + * @param parameters parameter list + * @param kind kind of function as in {@link FunctionNode.Kind} + * @param flags initial flags + */ + public FunctionNode( + final Source source, + final long token, + final int finish, + final long firstToken, + final Namespace namespace, + final IdentNode ident, + final String name, + final List<IdentNode> parameters, + final FunctionNode.Kind kind, + final int flags) { super(source, token, finish); this.ident = ident; this.name = name; - this.kind = Kind.NORMAL; - this.parameters = new ArrayList<>(); - this.firstToken = token; + this.kind = kind; + this.parameters = parameters; + this.firstToken = firstToken; this.lastToken = token; this.namespace = namespace; - this.labelStack = new Stack<>(); - this.controlStack = new Stack<>(); this.compilationState = EnumSet.of(CompilationState.INITIALIZED); - this.specializedTypes = new HashMap<>(); + this.declaredSymbols = new HashSet<>(); + this.flags = flags; + this.compileUnit = null; + this.body = null; } - private FunctionNode(final FunctionNode functionNode, final CopyState cs) { - super(functionNode, cs); - - this.ident = (IdentNode)cs.existingOrCopy(functionNode.ident); - this.name = functionNode.name; + private FunctionNode(final FunctionNode functionNode, final long lastToken, final int flags, final Type returnType, final CompileUnit compileUnit, final EnumSet<CompilationState> compilationState, final Block body) { + super(functionNode); + this.flags = flags; + this.returnType = returnType; + this.compileUnit = compileUnit; + this.lastToken = lastToken; + this.compilationState = compilationState; + this.body = body; + + // the fields below never change - they are final and assigned in constructor + this.name = functionNode.name; + this.ident = functionNode.ident; + this.namespace = functionNode.namespace; + this.declaredSymbols = functionNode.declaredSymbols; this.kind = functionNode.kind; - - this.parameters = new ArrayList<>(); - for (final IdentNode param : functionNode.getParameters()) { - this.parameters.add((IdentNode)cs.existingOrCopy(param)); - } - - this.firstToken = functionNode.firstToken; - this.lastToken = functionNode.lastToken; - this.namespace = functionNode.getNamespace(); - this.thisNode = (IdentNode)cs.existingOrCopy(functionNode.thisNode); - this.scopeNode = (IdentNode)cs.existingOrCopy(functionNode.scopeNode); - this.resultNode = (IdentNode)cs.existingOrCopy(functionNode.resultNode); - this.argumentsNode = (IdentNode)cs.existingOrCopy(functionNode.argumentsNode); - this.varArgsNode = (IdentNode)cs.existingOrCopy(functionNode.varArgsNode); - this.calleeNode = (IdentNode)cs.existingOrCopy(functionNode.calleeNode); - this.labelStack = new Stack<>(); - this.controlStack = new Stack<>(); - - this.flags = functionNode.flags; - - this.funcVarNode = (VarNode)cs.existingOrCopy(functionNode.funcVarNode); - /** VarNode for this function statement */ - - this.compilationState = EnumSet.copyOf(functionNode.compilationState); - this.specializedTypes = new HashMap<>(); + this.parameters = functionNode.parameters; + this.firstToken = functionNode.firstToken; } @Override - protected Node copy(final CopyState cs) { - // deep clone all parent blocks - return new FunctionNode(this, cs); - } - - @Override - public Node accept(final NodeVisitor visitor) { - final FunctionNode saveFunctionNode = visitor.getCurrentFunctionNode(); - final Block saveBlock = visitor.getCurrentBlock(); - final MethodEmitter saveMethodEmitter = visitor.getCurrentMethodEmitter(); - final CompileUnit saveCompileUnit = visitor.getCurrentCompileUnit(); - - visitor.setCurrentFunctionNode(this); - visitor.setCurrentBlock(this); - - try { - if (visitor.enterFunctionNode(this) != null) { - if (ident != null) { - ident = (IdentNode)ident.accept(visitor); - } - - for (int i = 0, count = parameters.size(); i < count; i++) { - parameters.set(i, (IdentNode)parameters.get(i).accept(visitor)); - } - - for (int i = 0, count = statements.size(); i < count; i++) { - statements.set(i, statements.get(i).accept(visitor)); - } - - return visitor.leaveFunctionNode(this); - } - } finally { - visitor.setCurrentBlock(saveBlock); - visitor.setCurrentFunctionNode(saveFunctionNode); - visitor.setCurrentCompileUnit(saveCompileUnit); - visitor.setCurrentMethodEmitter(saveMethodEmitter); + public Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterFunctionNode(this)) { + return visitor.leaveFunctionNode(setBody(lc, (Block)body.accept(visitor))); } - return this; } - @Override - public boolean needsScope() { - return super.needsScope() || isProgram(); + /** + * Get the compilation state of this function + * @return the compilation state + */ + public EnumSet<CompilationState> getState() { + return compilationState; } /** @@ -357,62 +297,17 @@ public class FunctionNode extends Block { * FunctionNode has been lowered, the compiler will add * {@code CompilationState#LOWERED} to the state vector * + * @param lc lexical context * @param state {@link CompilationState} to add + * @return function node or a new one if state was changed */ - public void setState(final CompilationState state) { - compilationState.add(state); - } - - /* - * Frame management. - */ - - /** - * Push a new block frame. - * - * @return the new frame - */ - public final Frame pushFrame() { - frames = new Frame(frames); - return frames; - } - - /** - * Pop a block frame. - */ - public final void popFrame() { - frames = frames.getPrevious(); - } - - /** - * Create a temporary variable to the current frame. - * - * @param currentFrame Frame to add to - defaults to current function frame - * @param type Strong type of symbol. - * @param node Primary node to use symbol. - * - * @return Symbol used. - */ - public Symbol newTemporary(final Frame currentFrame, final Type type, final Node node) { - assert currentFrame != null; - Symbol symbol = node.getSymbol(); - - // If no symbol already present. - if (symbol == null) { - final String uname = uniqueName(TEMP_PREFIX.tag()); - symbol = new Symbol(uname, IS_TEMP, type); - symbol.setNode(node); - } - - // Assign a slot if it doesn't have one. - if (!symbol.hasSlot()) { - currentFrame.addSymbol(symbol); + public FunctionNode setState(final LexicalContext lc, final CompilationState state) { + if (this.compilationState.equals(state)) { + return this; } - - // Set symbol to node. - node.setSymbol(symbol); - - return symbol; + final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState); + newState.add(state); + return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, newState, body)); } /** @@ -425,18 +320,6 @@ public class FunctionNode extends Block { } /** - * Add a new temporary variable to the current frame - * - * @param type Strong type of symbol - * @param node Primary node to use symbol - * - * @return symbol used - */ - public Symbol newTemporary(final Type type, final Node node) { - return newTemporary(frames, type, node); - } - - /** * Create a virtual symbol for a literal. * * @param literalNode Primary node to use symbol. @@ -444,9 +327,8 @@ public class FunctionNode extends Block { * @return Symbol used. */ public Symbol newLiteral(final LiteralNode<?> literalNode) { - final String uname = uniqueName(LITERAL_PREFIX.tag()); + final String uname = uniqueName(LITERAL_PREFIX.symbolName()); final Symbol symbol = new Symbol(uname, IS_CONSTANT, literalNode.getType()); - symbol.setNode(literalNode); literalNode.setSymbol(symbol); return symbol; @@ -482,84 +364,43 @@ public class FunctionNode extends Block { sb.append(')'); } - /** - * Returns true if the function is the top-level program. - * @return True if this function node represents the top-level program. - */ - public boolean isProgram() { - return (flags & IS_PROGRAM) != 0; - } - - /** - * Marks the function as representing the top-level program. - */ - public void setProgram() { - flags |= IS_PROGRAM; + @Override + public boolean getFlag(final int flag) { + return (flags & flag) != 0; } - /** - * Get the control stack. Used when parsing to establish nesting depths of - * different control structures - * - * @return the control stack - */ - public Stack<Node> getControlStack() { - return controlStack; + @Override + public FunctionNode setFlags(final LexicalContext lc, int flags) { + if (this.flags == flags) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, compilationState, body)); } - /** - * Should this function node be lazily code generated, i.e. first at link time - * @return true if lazy - */ - public boolean isLazy() { - return (flags & IS_LAZY) != 0; + @Override + public FunctionNode clearFlag(final LexicalContext lc, final int flag) { + return setFlags(lc, flags & ~flag); } - /** - * Set if this function should be lazily generated - * @param isLazy is lazy - */ - public void setIsLazy(final boolean isLazy) { - this.flags = isLazy ? flags | IS_LAZY : flags & ~IS_LAZY; + @Override + public FunctionNode setFlag(final LexicalContext lc, final int flag) { + return setFlags(lc, flags | flag); } /** - * Check if the {@code with} keyword is used in this function - * - * @return true if {@code with} is used + * Returns true if the function is the top-level program. + * @return True if this function node represents the top-level program. */ - public boolean hasWith() { - return (flags & HAS_WITH) != 0; + public boolean isProgram() { + return getFlag(IS_PROGRAM); } /** - * Flag this function as using the {@code with} keyword - * @param ancestors the iterator over functions in this functions's containing lexical context + * Should this function node be lazily code generated, i.e. first at link time + * @return true if lazy */ - public void setHasWith(final Iterator<FunctionNode> ancestors) { - if(!hasWith()) { - this.flags |= HAS_WITH; - // with requires scope in parents. - // TODO: refine this. with should not force all variables in parents to be in scope, only those that are - // actually referenced as identifiers by name - markParentForWithOrEval(ancestors); - } - } - - private void markParentForWithOrEval(final Iterator<FunctionNode> ancestors) { - // If this is invoked, then either us or a descendant uses with or eval, meaning we must have our own scope. - setNeedsScope(); - - if(ancestors.hasNext()) { - ancestors.next().setDescendantHasWithOrEval(ancestors); - } - } - - private void setDescendantHasWithOrEval(final Iterator<FunctionNode> ancestors) { - if((flags & HAS_DESCENDANT_WITH_OR_EVAL) == 0) { - flags |= HAS_DESCENDANT_WITH_OR_EVAL; - markParentForWithOrEval(ancestors); - } + public boolean isLazy() { + return getFlag(IS_LAZY); } /** @@ -568,30 +409,7 @@ public class FunctionNode extends Block { * @return true if {@code eval} is used */ public boolean hasEval() { - return (flags & HAS_EVAL) != 0; - } - - /** - * Flag this function as calling the {@code eval} function - * @param ancestors the iterator over functions in this functions's containing lexical context - */ - public void setHasEval(final Iterator<FunctionNode> ancestors) { - if(!hasEval()) { - this.flags |= HAS_EVAL; - markParentForWithOrEval(ancestors); - } - } - - /** - * Test whether this function or any of its nested functions contains a <tt>with</tt> statement - * or an <tt>eval</tt> call. - * - * @see #hasWith() - * @see #hasEval() - * @return true if this or a nested function contains with or eval - */ - public boolean hasDeepWithOrEval() { - return (flags & HAS_DEEP_WITH_OR_EVAL) != 0; + return getFlag(HAS_EVAL); } /** @@ -603,90 +421,11 @@ public class FunctionNode extends Block { } /** - * Set the first token for this function - * @param firstToken the first token - */ - public void setFirstToken(final long firstToken) { - this.firstToken = firstToken; - } - - /** - * Returns a list of functions declared by this function. Only includes declared functions, and does not include any - * function expressions that might occur in its body. - * @return a list of functions declared by this function. - */ - public List<FunctionNode> getDeclaredFunctions() { - // Note that the function does not have a dedicated list of declared functions, but rather relies on the - // invariant that all function declarations are at the beginning of the statement list as VarNode with a - // FunctionNode marked as statement with its variable initializer. Every VarNode is also preceded by a - // LineNumberNode. This invariant is established by the parser and has to be preserved in visitors. - final List<FunctionNode> fns = new ArrayList<>(); - for (final Node stmt : statements) { - if(stmt instanceof LineNumberNode) { - continue; - } else if(stmt instanceof VarNode) { - final Node init = ((VarNode)stmt).getInit(); - if(init instanceof FunctionNode) { - final FunctionNode fn = (FunctionNode)init; - if(fn.isDeclared()) { - fns.add(fn); - continue; - } - } - } - // Node is neither a LineNumberNode, nor a function declaration VarNode. Since all function declarations are - // at the start of the function, we've reached the end of function declarations. - break; - } - return fns; - } - - /** - * Get the label stack. This is used by the parser to establish - * label nesting depth - * - * @return the label stack - */ - public Stack<LabelNode> getLabelStack() { - return labelStack; - } - - /** - * If this function needs to use var args, return the identifier to the node used - * for the var args structure - * - * @return IdentNode representing the var args structure - */ - public IdentNode getVarArgsNode() { - return varArgsNode; - } - - /** - * Set the identifier to the node used for the var args structure - * - * @param varArgsNode IdentNode representing the var args - */ - public void setVarArgsNode(final IdentNode varArgsNode) { - this.varArgsNode = varArgsNode; - } - - /** - * If this function uses the {@code callee} variable, return the node used - * as this variable - * - * @return an IdentNode representing the {@code callee} variable + * Check whether this function has nested function declarations + * @return true if nested function declarations exist */ - public IdentNode getCalleeNode() { - return calleeNode; - } - - /** - * If this function uses the {@code callee} variable, set the node representing the - * callee - * @param calleeNode an IdentNode representing the callee - */ - public void setCalleeNode(final IdentNode calleeNode) { - this.calleeNode = calleeNode; + public boolean hasDeclaredFunctions() { + return getFlag(HAS_FUNCTION_DECLARATIONS); } /** @@ -697,42 +436,54 @@ public class FunctionNode extends Block { * @return true if the function's generated Java method needs a {@code callee} parameter. */ public boolean needsCallee() { - return needsParentScope() || needsSelfSymbol() || (needsArguments() && !isStrictMode()); + return needsParentScope() || needsSelfSymbol() || (needsArguments() && !isStrict()); } /** - * If this is a function where {@code arguments} is used, return the node used as the {@code arguments} - * variable - * @return an IdentNode representing {@code arguments} + * Get the identifier for this function + * @return the identifier as an IdentityNode */ - public IdentNode getArgumentsNode() { - return argumentsNode; + public IdentNode getIdent() { + return ident; } /** - * If this is a Function where {@code arguments} is used, an identifier to the node representing - * the {@code arguments} value has to be supplied by the compiler - * - * @param argumentsNode IdentNode that represents {@code arguments} + * Return a set of symbols declared in this function node. This + * is only relevant after Attr, otherwise it will be an empty + * set as no symbols have been introduced + * @return set of declared symbols in function */ - public void setArgumentsNode(final IdentNode argumentsNode) { - this.argumentsNode = argumentsNode; + public Set<Symbol> getDeclaredSymbols() { + return Collections.unmodifiableSet(declaredSymbols); } /** - * Get the identifier for this function - * @return the identifier as an IdentityNode + * Add a declared symbol to this function node + * @param symbol symbol that is declared */ - public IdentNode getIdent() { - return ident; + public void addDeclaredSymbol(final Symbol symbol) { + declaredSymbols.add(symbol); + } + + /** + * Get the function body + * @return the function body + */ + public Block getBody() { + return body; } /** - * Reset the identifier for this function - * @param ident IdentNode for new identifier + * Reset the function body + * @param lc lexical context + * @param body new body + * @return new function node if body changed, same if not */ - public void setIdent(final IdentNode ident) { - this.ident = ident; + public FunctionNode setBody(final LexicalContext lc, final Block body) { + if(this.body == body) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, compilationState, body)); } /** @@ -748,17 +499,6 @@ public class FunctionNode extends Block { } /** - * Flag this function as one that defines the identifier "arguments" as a function parameter or nested function - * name. This precludes it from needing to have an Arguments object defined as "arguments" local variable. Note that - * defining a local variable named "arguments" still requires construction of the Arguments object (see - * ECMAScript 5.1 Chapter 10.5). - * @see #needsArguments() - */ - public void setDefinesArguments() { - this.flags |= DEFINES_ARGUMENTS; - } - - /** * Returns true if this function needs to have an Arguments object defined as a local variable named "arguments". * Functions that use "arguments" as identifier and don't define it as a name of a parameter or a nested function * (see ECMAScript 5.1 Chapter 10.5), as well as any function that uses eval or with, or has a nested function that @@ -770,15 +510,7 @@ public class FunctionNode extends Block { public boolean needsArguments() { // uses "arguments" or calls eval, but it does not redefine "arguments", and finally, it's not a script, since // for top-level script, "arguments" is picked up from Context by Global.init() instead. - return (flags & MAYBE_NEEDS_ARGUMENTS) != 0 && (flags & DEFINES_ARGUMENTS) == 0 && !isProgram(); - } - - /** - * Flags this function as one that uses the "arguments" identifier. - * @see #needsArguments() - */ - public void setUsesArguments() { - flags |= USES_ARGUMENTS; + return getFlag(MAYBE_NEEDS_ARGUMENTS) && !getFlag(DEFINES_ARGUMENTS) && !isProgram(); } /** @@ -789,7 +521,7 @@ public class FunctionNode extends Block { * @return true if the function needs parent scope. */ public boolean needsParentScope() { - return (flags & NEEDS_PARENT_SCOPE) != 0 || isProgram(); + return getFlag(NEEDS_PARENT_SCOPE) || isProgram(); } /** @@ -802,15 +534,6 @@ public class FunctionNode extends Block { } /** - * Set the kind of this function - * @see FunctionNode.Kind - * @param kind the kind - */ - public void setKind(final Kind kind) { - this.kind = kind; - } - - /** * Return the last token for this function's code * @return last token */ @@ -820,10 +543,15 @@ public class FunctionNode extends Block { /** * Set the last token for this function's code + * @param lc lexical context * @param lastToken the last token + * @return function node or a new one if state was changed */ - public void setLastToken(final long lastToken) { - this.lastToken = lastToken; + public FunctionNode setLastToken(final LexicalContext lc, final long lastToken) { + if (this.lastToken == lastToken) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, compilationState, body)); } /** @@ -835,21 +563,13 @@ public class FunctionNode extends Block { } /** - * Set the name of this function - * @param name the name - */ - public void setName(final String name) { - this.name = name; - } - - /** * Check if this function should have all its variables in its own scope. Scripts, split sub-functions, and * functions having with and/or eval blocks are such. * * @return true if all variables should be in scope */ public boolean allVarsInScope() { - return isProgram() || (flags & HAS_ALL_VARS_IN_SCOPE) != 0; + return isProgram() || getFlag(HAS_ALL_VARS_IN_SCOPE); } /** @@ -858,15 +578,7 @@ public class FunctionNode extends Block { * @return true if this function is split from a larger one */ public boolean isSplit() { - return (flags & IS_SPLIT) != 0; - } - - /** - * Flag this function node as being a sub-function generated by the splitter - */ - public void setIsSplit() { - this.flags |= IS_SPLIT; - setNeedsScope(); + return getFlag(IS_SPLIT); } /** @@ -875,15 +587,7 @@ public class FunctionNode extends Block { * @return true if there are lazy child functions */ public boolean hasLazyChildren() { - return (flags & HAS_LAZY_CHILDREN) != 0; - } - - /** - * Flag this function node as having yet-to-be-generated child functions - */ - public void setHasLazyChildren() { - this.flags |= HAS_LAZY_CHILDREN; - setNeedsScope(); + return getFlag(HAS_LAZY_CHILDREN); } /** @@ -895,66 +599,13 @@ public class FunctionNode extends Block { } /** - * Set the paremeters to this function - * @param parameters a list of IdentNodes representing parameters in left to right order - */ - public void setParameters(final List<IdentNode> parameters) { - this.parameters = parameters; - } - - /** * Get a specialized type for an identity, if one exists * @param node node to check specialized type for * @return null if no specialization exists, otherwise type */ + @SuppressWarnings("static-method") public Type getSpecializedType(final IdentNode node) { - return specializedTypes.get(node); - } - - /** - * Set parameter type hints for specialization. - * @param types types array of length equal to parameter list size - */ - public void setParameterTypes(final Class<?>[] types) { - assert types.length == parameters.size() : "Type vector length doesn't correspond to parameter types"; - //diff - skip the callee and this etc, they are not explicit params in the parse tree - for (int i = 0; i < types.length ; i++) { - specializedTypes.put(parameters.get(i), Type.typeFor(types[i])); - } - } - - /** - * Get the identifier for the variable in which the function return value - * should be stored - * @return an IdentNode representing the return value - */ - public IdentNode getResultNode() { - return resultNode; - } - - /** - * Set the identifier representing the variable in which the function return - * value should be stored - * @param resultNode an IdentNode representing the return value - */ - public void setResultNode(final IdentNode resultNode) { - this.resultNode = resultNode; - } - - /** - * Get the identifier representing this function's scope - * @return an IdentNode representing this function's scope - */ - public IdentNode getScopeNode() { - return scopeNode; - } - - /** - * Set the identifier representing this function's scope - * @param scopeNode an IdentNode representing this function's scope - */ - public void setScopeNode(final IdentNode scopeNode) { - this.scopeNode = scopeNode; + return null; //TODO implement specialized types later } /** @@ -962,15 +613,7 @@ public class FunctionNode extends Block { * @return true if function is declared. */ public boolean isDeclared() { - return (flags & IS_DECLARED) != 0; - } - - /** - * Flag this function as being created as a function declaration (as opposed to a function expression). - * @see Parser - */ - public void setIsDeclared() { - this.flags |= IS_DECLARED; + return getFlag(IS_DECLARED); } /** @@ -978,15 +621,7 @@ public class FunctionNode extends Block { * @return true if function is anonymous */ public boolean isAnonymous() { - return (flags & IS_ANONYMOUS) != 0; - } - - /** - * Flag this function as an anonymous function. - * @see Parser - */ - public void setIsAnonymous() { - this.flags |= IS_ANONYMOUS; + return getFlag(IS_ANONYMOUS); } /** @@ -995,109 +630,7 @@ public class FunctionNode extends Block { * @return true if function needs a symbol for self */ public boolean needsSelfSymbol() { - return (flags & NEEDS_SELF_SYMBOL) != 0; - } - - /** - * Get the initializer statement for the __callee__ variable, where applicable - * for self references - * @return initialization - */ - public Node getSelfSymbolInit() { - return this.selfSymbolInit; - } - - /** - * Flag the function as needing a self symbol. This is needed only for - * self referring functions - * @param selfSymbolInit initialization expression for self symbol - */ - public void setNeedsSelfSymbol(final Node selfSymbolInit) { - this.flags |= NEEDS_SELF_SYMBOL; - this.selfSymbolInit = selfSymbolInit; - } - - /** - * Marks this function as using any of its ancestors' scopes. - */ - public void setUsesAncestorScope() { - this.flags |= USES_ANCESTOR_SCOPE; - } - - @Override - void setUsesParentScopeSymbol(Symbol symbol, Iterator<Block> ancestors) { - setUsesAncestorScope(); - super.setUsesParentScopeSymbol(symbol, ancestors); - } - - /** - * Return the node representing {@code this} in this function - * @return IdentNode representing {@code this} - */ - public IdentNode getThisNode() { - return thisNode; - } - - /** - * Set the node representing {@code this} in this function - * @param thisNode identifier representing {@code this} - */ - public void setThisNode(final IdentNode thisNode) { - this.thisNode = thisNode; - } - - /** - * Every function declared as {@code function x()} is internally hoisted - * and represented as {@code var x = function() ... }. This getter returns - * the VarNode representing this virtual assignment - * - * @return the var node emitted for setting this function symbol - */ - public VarNode getFunctionVarNode() { - return funcVarNode; - } - - /** - * Set the virtual VarNode assignment for this function. - * @see FunctionNode#getFunctionVarNode() - * - * @param varNode the virtual var node assignment - */ - public void setFunctionVarNode(final VarNode varNode) { - funcVarNode = varNode; - } - - /** - * The line number information where the function was declared must be propagated - * to the virtual {@code var x = function() ... } assignment described in - * {@link FunctionNode#getFunctionVarNode()} - * This maintains the line number of the declaration - * - * @return a line number node representing the line this function was declared - */ - public LineNumberNode getFunctionVarLineNumberNode() { - return funcVarLineNumberNode; - } - - /** - * Set the virtual VarNode assignment for this function, along with - * a line number node for tracking the original start line of the function - * declaration - * - * @param varNode the virtual var node assignment - * @param lineNumber the line number node for the function declaration - */ - public void setFunctionVarNode(final VarNode varNode, final LineNumberNode lineNumber) { - funcVarNode = varNode; - funcVarLineNumberNode = lineNumber; - } - - /** - * Get the namespace this function uses for its symbols - * @return the namespace - */ - public Namespace getNamespace() { - return namespace; + return body.getFlag(Block.NEEDS_SELF_SYMBOL); } @Override @@ -1118,47 +651,38 @@ public class FunctionNode extends Block { /** * Set the function return type - * + * @param lc lexical context * @param returnType new return type + * @return function node or a new one if state was changed */ - public void setReturnType(final Type returnType) { + public FunctionNode setReturnType(final LexicalContext lc, final Type returnType) { //we never bother with object types narrower than objects, that will lead to byte code verification errors //as for instance even if we know we are returning a string from a method, the code generator will always //treat it as an object, at least for now - this.returnType = Type.widest(this.returnType, returnType.isObject() ? Type.OBJECT : returnType); - } - - /** - * Set strict mode on or off for this function - * - * @param isStrictMode true if strict mode should be enabled - */ - public void setStrictMode(final boolean isStrictMode) { - flags = isStrictMode ? flags | IS_STRICT_MODE : flags & ~IS_STRICT_MODE; + if (this.returnType == returnType) { + return this; + } + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + flags, + Type.widest(this.returnType, returnType.isObject() ? + Type.OBJECT : + returnType), + compileUnit, + compilationState, + body)); } /** * Check if the function is generated in strict mode * @return true if strict mode enabled for function */ - public boolean isStrictMode() { - return (flags & IS_STRICT_MODE) != 0; - } - - /** - * Set the lowered state - */ - public void setIsLowered() { - flags |= IS_LOWERED; - } - - /** - * Get the lowered state - * - * @return true if function is lowered - */ - public boolean isLowered() { - return (flags & IS_LOWERED) != 0; + public boolean isStrict() { + return getFlag(IS_STRICT); } /** @@ -1173,25 +697,47 @@ public class FunctionNode extends Block { /** * Reset the compile unit used to compile this function * @see Compiler + * @param lc lexical context * @param compileUnit the compile unit + * @return function node or a new one if state was changed */ - public void setCompileUnit(final CompileUnit compileUnit) { - this.compileUnit = compileUnit; + public FunctionNode setCompileUnit(final LexicalContext lc, final CompileUnit compileUnit) { + if (this.compileUnit == compileUnit) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, compilationState, body)); } /** - * Return the method emitter used to write bytecode for this function - * @return the method emitter + * Create a temporary variable to the current frame. + * + * @param block that needs the temporary + * @param type Strong type of symbol. + * @param node Primary node to use symbol. + * + * @return Symbol used. */ - public MethodEmitter getMethodEmitter() { - return method; + public Symbol ensureSymbol(final Block block, final Type type, final Node node) { + Symbol symbol = node.getSymbol(); + + // If no symbol already present. + if (symbol == null) { + final String uname = uniqueName(TEMP_PREFIX.symbolName()); + symbol = new Symbol(uname, IS_TEMP, type); + block.putSymbol(uname, symbol); + node.setSymbol(symbol); + } + + return symbol; } /** - * Set the method emitter that is to be used to write bytecode for this function - * @param method a method emitter + * Get the symbol for a compiler constant, or null if not available (yet) + * @param cc compiler constant + * @return symbol for compiler constant, or null if not defined yet (for example in Lower) */ - public void setMethodEmitter(final MethodEmitter method) { - this.method = method; + public Symbol compilerConstant(final CompilerConstants cc) { + return body.getExistingSymbol(cc.symbolName()); } + } diff --git a/src/jdk/nashorn/internal/ir/IdentNode.java b/src/jdk/nashorn/internal/ir/IdentNode.java index 889a8700..daf79ee3 100644 --- a/src/jdk/nashorn/internal/ir/IdentNode.java +++ b/src/jdk/nashorn/internal/ir/IdentNode.java @@ -32,13 +32,15 @@ import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS; import jdk.nashorn.internal.codegen.ObjectClassGenerator; 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.runtime.Source; /** * IR representation for an identifier. */ -public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNode>, FunctionCall { +@Immutable +public final class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNode>, FunctionCall { private static final int PROPERTY_NAME = 1 << 0; private static final int INITIALIZED_HERE = 1 << 1; private static final int FUNCTION = 1 << 2; @@ -47,9 +49,9 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo private final String name; /** Type for a callsite, e.g. X in a get()X or a set(X)V */ - private Type callSiteType; + private final Type callSiteType; - private byte flags; + private final int flags; /** * Constructor @@ -62,6 +64,15 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo public IdentNode(final Source source, final long token, final int finish, final String name) { super(source, token, finish); this.name = name; + this.callSiteType = null; + this.flags = 0; + } + + private IdentNode(final IdentNode identNode, final String name, final Type callSiteType, final int flags) { + super(identNode); + this.name = name; + this.callSiteType = callSiteType; + this.flags = flags; } /** @@ -71,8 +82,9 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo */ public IdentNode(final IdentNode identNode) { super(identNode); - this.name = identNode.getName(); - this.flags = identNode.flags; + this.name = identNode.getName(); + this.callSiteType = null; + this.flags = identNode.flags; } @Override @@ -92,40 +104,15 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo @Override public IdentNode setType(final Type type) { - if (DEBUG_FIELDS && getSymbol() != null && !Type.areEquivalent(getSymbol().getSymbolType(), type)) { - ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType()); - } // do NOT, repeat NOT touch the symbol here. it might be a local variable or whatever. This is the override if it isn't - if(this.callSiteType == type) { + if (this.callSiteType == type) { return this; } - final IdentNode n = (IdentNode)clone(); - n.callSiteType = type; - return n; - } - - @Override - protected Node copy(final CopyState cs) { - return new IdentNode(this); - } - - /** - * Test to see if two IdentNode are the same. - * - * @param other Other ident. - * @return true if the idents are the same. - */ - @Override - public boolean equals(final Object other) { - if (other instanceof IdentNode) { - return name.equals(((IdentNode)other).name); + if (DEBUG_FIELDS && getSymbol() != null && !Type.areEquivalent(getSymbol().getSymbolType(), type)) { + ObjectClassGenerator.LOG.info(getClass().getName(), " ", this, " => ", type, " instead of ", getType()); } - return false; - } - @Override - public int hashCode() { - return name.hashCode(); + return new IdentNode(this, name, type, flags); } /** @@ -135,7 +122,7 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterIdentNode(this) != null) { + if (visitor.enterIdentNode(this)) { return visitor.leaveIdentNode(this); } @@ -147,7 +134,7 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo if (hasCallSiteType()) { sb.append('{'); final String desc = getType().getDescriptor(); - sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : getType().getDescriptor()); + sb.append(desc.charAt(desc.length() - 1) == ';' ? 'O' : getType().getDescriptor()); sb.append('}'); } @@ -191,10 +178,10 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo * @return a node equivalent to this one except for the requested change. */ public IdentNode setIsPropertyName() { - if(isPropertyName()) return this; - final IdentNode n = (IdentNode)clone(); - n.flags |= PROPERTY_NAME; - return n; + if (isPropertyName()) { + return this; + } + return new IdentNode(this, name, callSiteType, flags | PROPERTY_NAME); } /** @@ -210,10 +197,10 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo * @return a node equivalent to this one except for the requested change. */ public IdentNode setIsInitializedHere() { - if(isInitializedHere()) return this; - final IdentNode n = (IdentNode)clone(); - n.flags |= INITIALIZED_HERE; - return n; + if (isInitializedHere()) { + return this; + } + return new IdentNode(this, name, callSiteType, flags | INITIALIZED_HERE); } /** @@ -223,7 +210,7 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo * @return true if this IdentNode is special */ public boolean isSpecialIdentity() { - return name.equals(__DIR__.tag()) || name.equals(__FILE__.tag()) || name.equals(__LINE__.tag()); + return name.equals(__DIR__.symbolName()) || name.equals(__FILE__.symbolName()) || name.equals(__LINE__.symbolName()); } @Override @@ -236,9 +223,9 @@ public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNo * @return an ident node identical to this one in all aspects except with its function flag set. */ public IdentNode setIsFunction() { - if(isFunction()) return this; - final IdentNode n = (IdentNode)clone(); - n.flags |= FUNCTION; - return n; + if (isFunction()) { + return this; + } + return new IdentNode(this, name, callSiteType, flags | FUNCTION); } } diff --git a/src/jdk/nashorn/internal/ir/IfNode.java b/src/jdk/nashorn/internal/ir/IfNode.java index 3ddcf1dc..027e1a84 100644 --- a/src/jdk/nashorn/internal/ir/IfNode.java +++ b/src/jdk/nashorn/internal/ir/IfNode.java @@ -25,22 +25,23 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for an IF statement. - * */ -public class IfNode extends Node { +@Immutable +public final class IfNode extends Node { /** Test expression. */ - private Node test; + private final Node test; /** Pass statements. */ - private Block pass; + private final Block pass; /** Fail statements. */ - private Block fail; + private final Block fail; /** * Constructor @@ -54,37 +55,30 @@ public class IfNode extends Node { */ public IfNode(final Source source, final long token, final int finish, final Node test, final Block pass, final Block fail) { super(source, token, finish); - this.test = test; this.pass = pass; this.fail = fail; } - private IfNode(final IfNode ifNode, final CopyState cs) { + private IfNode(final IfNode ifNode, final Node test, final Block pass, final Block fail) { super(ifNode); - - this.test = cs.existingOrCopy(ifNode.test); - this.pass = (Block)cs.existingOrCopy(ifNode.pass); - this.fail = (Block)cs.existingOrCopy(ifNode.fail); + this.test = test; + this.pass = pass; + this.fail = fail; } @Override - protected Node copy(final CopyState cs) { - return new IfNode(this, cs); + public boolean isTerminal() { + return pass.isTerminal() && fail != null && fail.isTerminal(); } @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterIfNode(this) != null) { - test = test.accept(visitor); - - pass = (Block)pass.accept(visitor); - - if (fail != null) { - fail = (Block)fail.accept(visitor); - } - - return visitor.leaveIfNode(this); + if (visitor.enterIfNode(this)) { + return visitor.leaveIfNode( + setTest(test.accept(visitor)). + setPass((Block)pass.accept(visitor)). + setFail(fail == null ? null : (Block)fail.accept(visitor))); } return this; @@ -105,6 +99,13 @@ public class IfNode extends Node { return fail; } + private IfNode setFail(final Block fail) { + if (this.fail == fail) { + return this; + } + return new IfNode(this, test, pass, fail); + } + /** * Get the then block for this IfNode * @return the then block @@ -113,6 +114,13 @@ public class IfNode extends Node { return pass; } + private IfNode setPass(final Block pass) { + if (this.pass == pass) { + return this; + } + return new IfNode(this, test, pass, fail); + } + /** * Get the test expression for this IfNode * @return the test expression @@ -124,8 +132,12 @@ public class IfNode extends Node { /** * Reset the test expression for this IfNode * @param test a new test expression + * @return new or same IfNode */ - public void setTest(final Node test) { - this.test = test; + public IfNode setTest(final Node test) { + if (this.test == test) { + return this; + } + return new IfNode(this, test, pass, fail); } } diff --git a/src/jdk/nashorn/internal/ir/IndexNode.java b/src/jdk/nashorn/internal/ir/IndexNode.java index 4745bf64..764ee38c 100644 --- a/src/jdk/nashorn/internal/ir/IndexNode.java +++ b/src/jdk/nashorn/internal/ir/IndexNode.java @@ -25,22 +25,18 @@ package jdk.nashorn.internal.ir; -import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS; - -import jdk.nashorn.internal.codegen.ObjectClassGenerator; 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.runtime.Source; /** * IR representation of an indexed access (brackets operator.) - * */ -public class IndexNode extends BaseNode implements TypeOverride<IndexNode> { - /** Property ident. */ - private Node index; - - private boolean hasCallSiteType; +@Immutable +public final class IndexNode extends BaseNode { + /** Property index. */ + private final Node index; /** * Constructors @@ -52,50 +48,27 @@ public class IndexNode extends BaseNode implements TypeOverride<IndexNode> { * @param index index for access */ public IndexNode(final Source source, final long token, final int finish, final Node base, final Node index) { - super(source, token, finish, base); - + super(source, token, finish, base, false, false); this.index = index; } - /** - * Copy constructor - * - * @param indexNode source node - */ - public IndexNode(final IndexNode indexNode) { - this(indexNode, new CopyState()); - } - - private IndexNode(final IndexNode indexNode, final CopyState cs) { - super(indexNode, cs); - - index = cs.existingOrCopy(indexNode.index); - } - - @Override - protected Node copy(final CopyState cs) { - return new IndexNode(this, cs); - } - - @Override - public boolean equals(final Object other) { - if (!super.equals(other)) { - return false; - } - return index.equals(((IndexNode)other).getIndex()); - } - - @Override - public int hashCode() { - return super.hashCode() ^ getIndex().hashCode(); + private IndexNode(final IndexNode indexNode, final Node base, final Node index, final boolean isFunction, final boolean hasCallSiteType) { + super(indexNode, base, isFunction, hasCallSiteType); + this.index = index; } @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterIndexNode(this) != null) { - base = base.accept(visitor); - index = index.accept(visitor); - return visitor.leaveIndexNode(this); + if (visitor.enterIndexNode(this)) { + final Node newBase = base.accept(visitor); + final Node newIndex = index.accept(visitor); + final IndexNode newNode; + if (newBase != base || newIndex != index) { + newNode = new IndexNode(this, newBase, newIndex, isFunction(), hasCallSiteType()); + } else { + newNode = this; + } + return visitor.leaveIndexNode(newNode); } return this; @@ -105,7 +78,7 @@ public class IndexNode extends BaseNode implements TypeOverride<IndexNode> { public void toString(final StringBuilder sb) { final boolean needsParen = tokenType().needsParens(base.tokenType(), true); - if (hasCallSiteType) { + if (hasCallSiteType()) { sb.append('{'); final String desc = getType().getDescriptor(); sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : getType().getDescriptor()); @@ -135,27 +108,19 @@ public class IndexNode extends BaseNode implements TypeOverride<IndexNode> { return index; } - /** - * Reset the index expression for this IndexNode - * @param index a new index expression - */ - public void setIndex(final Node index) { - this.index = index; - } - @Override - public IndexNode setType(final Type type) { - if (DEBUG_FIELDS && !Type.areEquivalent(getSymbol().getSymbolType(), type)) { - ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType()); + public BaseNode setIsFunction() { + if (isFunction()) { + return this; } - hasCallSiteType = true; - getSymbol().setTypeOverride(type); - return this; + return new IndexNode(this, base, index, true, hasCallSiteType()); } @Override - public boolean canHaveCallSiteType() { - return true; //carried by the symbol and always the same nodetype==symboltype + public IndexNode setType(final Type type) { + logTypeChange(type); + getSymbol().setTypeOverride(type); //always a temp so this is fine. + return new IndexNode(this, base, index, isFunction(), true); } } diff --git a/src/jdk/nashorn/internal/ir/LabelNode.java b/src/jdk/nashorn/internal/ir/LabelNode.java index 756ea2df..bf932db8 100644 --- a/src/jdk/nashorn/internal/ir/LabelNode.java +++ b/src/jdk/nashorn/internal/ir/LabelNode.java @@ -25,29 +25,20 @@ package jdk.nashorn.internal.ir; -import jdk.nashorn.internal.ir.annotations.Ignore; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for a labeled statement. - * */ - -public class LabelNode extends Node { +@Immutable +public final class LabelNode extends LexicalContextNode { /** Label ident. */ - private IdentNode label; + private final IdentNode label; /** Statements. */ - private Block body; - - /** Node to break from. */ - @Ignore - private Node breakNode; - - /** Node to continue. */ - @Ignore - private Node continueNode; + private final Block body; /** * Constructor @@ -65,26 +56,23 @@ public class LabelNode extends Node { this.body = body; } - private LabelNode(final LabelNode labelNode, final CopyState cs) { + private LabelNode(final LabelNode labelNode, final IdentNode label, final Block body) { super(labelNode); - - this.label = (IdentNode)cs.existingOrCopy(labelNode.label); - this.body = (Block)cs.existingOrCopy(labelNode.body); - this.breakNode = cs.existingOrSame(labelNode.breakNode); - this.continueNode = cs.existingOrSame(labelNode.continueNode); + this.label = label; + this.body = body; } @Override - protected Node copy(final CopyState cs) { - return new LabelNode(this, cs); + public boolean isTerminal() { + return body.isTerminal(); } @Override - public Node accept(final NodeVisitor visitor) { - if (visitor.enterLabelNode(this) != null) { - label = (IdentNode)label.accept(visitor); - body = (Block)body.accept(visitor); - return visitor.leaveLabelNode(this); + public Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterLabelNode(this)) { + return visitor.leaveLabelNode( + setLabel(visitor.getLexicalContext(), (IdentNode)label.accept(visitor)). + setBody(visitor.getLexicalContext(), (Block)body.accept(visitor))); } return this; @@ -106,44 +94,15 @@ public class LabelNode extends Node { /** * Reset the body of the node + * @param lc lexical context * @param body new body + * @return new for node if changed or existing if not */ - public void setBody(final Block body) { - this.body = body; - } - - /** - * Get the break node for this node - * @return the break node - */ - public Node getBreakNode() { - return breakNode; - } - - /** - * Reset the break node for this node - * @param breakNode the break node - */ - public void setBreakNode(final Node breakNode) { - assert breakNode instanceof BreakableNode || breakNode instanceof Block : "Invalid break node: " + breakNode; - this.breakNode = breakNode; - } - - /** - * Get the continue node for this node - * @return the continue node - */ - public Node getContinueNode() { - return continueNode; - } - - /** - * Reset the continue node for this node - * @param continueNode the continue node - */ - public void setContinueNode(final Node continueNode) { - assert continueNode instanceof WhileNode : "invalid continue node: " + continueNode; - this.continueNode = continueNode; + public LabelNode setBody(final LexicalContext lc, final Block body) { + if (this.body == body) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new LabelNode(this, label, body)); } /** @@ -154,4 +113,11 @@ public class LabelNode extends Node { return label; } + private LabelNode setLabel(final LexicalContext lc, final IdentNode label) { + if (this.label == label) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new LabelNode(this, label, body)); + } + } diff --git a/src/jdk/nashorn/internal/ir/LabeledNode.java b/src/jdk/nashorn/internal/ir/LabeledNode.java deleted file mode 100644 index a3783e92..00000000 --- a/src/jdk/nashorn/internal/ir/LabeledNode.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - -package jdk.nashorn.internal.ir; - -import jdk.nashorn.internal.ir.annotations.Ignore; -import jdk.nashorn.internal.runtime.Source; - -/** - * IR base class for break and continue. - * - */ -public abstract class LabeledNode extends Node { - /** Optional label. */ - @Ignore - protected final LabelNode labelNode; - - /** Target control node. */ - @Ignore - protected final Node targetNode; - - /** Try chain. */ - @Ignore - protected final TryNode tryChain; - - /** scope nesting level */ - protected int scopeNestingLevel; - - /** - * Constructor - * - * @param source the source - * @param token token - * @param finish finish - * @param labelNode the label node - * @param targetNode the place to break to - * @param tryChain the try chain - */ - public LabeledNode(final Source source, final long token, final int finish, final LabelNode labelNode, final Node targetNode, final TryNode tryChain) { - super(source, token, finish); - - this.labelNode = labelNode; - this.targetNode = targetNode; - this.tryChain = tryChain; - } - - /** - * Copy constructor - * - * @param labeledNode source node - * @param cs copy state - */ - protected LabeledNode(final LabeledNode labeledNode, final CopyState cs) { - super(labeledNode); - - this.labelNode = (LabelNode)cs.existingOrCopy(labeledNode.labelNode); - this.targetNode = cs.existingOrSame(labeledNode.targetNode); - this.tryChain = (TryNode)cs.existingOrSame(labeledNode.tryChain); - this.scopeNestingLevel = labeledNode.scopeNestingLevel; - } - - /** - * Get the label - * @return the label - */ - public LabelNode getLabel() { - return labelNode; - } - - /** - * Get the target node - * @return the target node - */ - public Node getTargetNode() { - return targetNode; - } - - /** - * Get the surrounding try chain - * @return the try chain - */ - public TryNode getTryChain() { - return tryChain; - } - - /** - * Get the scope nesting level - * @return nesting level - */ - public int getScopeNestingLevel() { - return scopeNestingLevel; - } - - /** - * Set scope nesting level - * @param level the new level - */ - public void setScopeNestingLevel(final int level) { - scopeNestingLevel = level; - } -} diff --git a/src/jdk/nashorn/internal/ir/LexicalContext.java b/src/jdk/nashorn/internal/ir/LexicalContext.java index 2db1f796..c3f096f5 100644 --- a/src/jdk/nashorn/internal/ir/LexicalContext.java +++ b/src/jdk/nashorn/internal/ir/LexicalContext.java @@ -1,96 +1,250 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ package jdk.nashorn.internal.ir; -import java.util.ArrayDeque; -import java.util.Deque; +import java.io.File; import java.util.Iterator; import java.util.NoSuchElementException; +import jdk.nashorn.internal.codegen.Label; +import jdk.nashorn.internal.runtime.Debug; +import jdk.nashorn.internal.runtime.Source; /** * A class that tracks the current lexical context of node visitation as a stack of {@link Block} nodes. Has special * methods to retrieve useful subsets of the context. + * + * This is implemented with a primitive array and a stack pointer, because it really makes a difference + * performance wise. None of the collection classes were optimal */ -public class LexicalContext implements Cloneable { - private final Deque<Block> lexicalContext; +public class LexicalContext { + private LexicalContextNode[] stack; + + private int[] flags; + private int sp; /** * Creates a new empty lexical context. */ public LexicalContext() { - lexicalContext = new ArrayDeque<>(); + stack = new LexicalContextNode[16]; + flags = new int[16]; } /** - * Pushes a new block on top of the context, making it the innermost open block. - * @param block the new block + * Set the flags for a lexical context node on the stack. Does not + * replace the flags, but rather adds to them + * + * @param node node + * @param flag new flag to set */ - public void push(Block block) { - //new Exception(block.toString()).printStackTrace(); - lexicalContext.push(block); + public void setFlag(final LexicalContextNode node, final int flag) { + if (flag != 0) { + for (int i = sp - 1; i >= 0; i--) { + if (stack[i] == node) { + flags[i] |= flag; + //System.err.println("Setting flag " + node + " " + flag); + return; + } + } + } + assert false; } /** - * Pops the innermost block off the context. - * @param the block expected to be popped, used to detect unbalanced pushes/pops + * Get the flags for a lexical context node on the stack + * @param node node + * @return the flags for the node */ - public void pop(Block block) { - final Block popped = lexicalContext.pop(); - assert popped == block; + public int getFlags(final LexicalContextNode node) { + for (int i = sp - 1; i >= 0; i--) { + if (stack[i] == node) { + return flags[i]; + } + } + throw new AssertionError("flag node not on context stack"); } /** - * Returns an iterator over all blocks in the context, with the top block (innermost lexical context) first. - * @return an iterator over all blocks in the context. + * Get the function body of a function node on the lexical context + * stack. This will trigger an assertion if node isn't present + * @param functionNode function node + * @return body of function node */ - public Iterator<Block> getBlocks() { - return lexicalContext.iterator(); + public Block getFunctionBody(final FunctionNode functionNode) { + for (int i = sp - 1; i >= 0 ; i--) { + if (stack[i] == functionNode) { + return (Block)stack[i + 1]; + } + } + throw new AssertionError(functionNode.getName() + " not on context stack"); } /** - * Returns an iterator over all functions in the context, with the top (innermost open) function first. - * @return an iterator over all functions in the context. + * Return all nodes in the LexicalContext + * @return all nodes */ - public Iterator<FunctionNode> getFunctions() { - return new FunctionIterator(getBlocks()); + public Iterator<LexicalContextNode> getAllNodes() { + return new NodeIterator<>(LexicalContextNode.class); + } + + /** + * Returns the outermost function in this context. It is either the program, or a lazily compiled function. + * @return the outermost function in this context. + */ + public FunctionNode getOutermostFunction() { + return (FunctionNode)stack[0]; } - private static final class FunctionIterator implements Iterator<FunctionNode> { - private final Iterator<Block> it; - private FunctionNode next; - FunctionIterator(Iterator<Block> it) { - this.it = it; - next = findNext(); + + /** + * Pushes a new block on top of the context, making it the innermost open block. + * @param node the new node + * @return the node that was pushed + */ + public <T extends LexicalContextNode> T push(final T node) { + if (sp == stack.length) { + final LexicalContextNode[] newStack = new LexicalContextNode[sp * 2]; + System.arraycopy(stack, 0, newStack, 0, sp); + stack = newStack; + + final int[] newFlags = new int[sp * 2]; + System.arraycopy(flags, 0, newFlags, 0, sp); + flags = newFlags; + } + stack[sp] = node; + flags[sp] = 0; - @Override - public boolean hasNext() { - return next != null; + sp++; + + return node; + } + + /** + * Is the context empty? + * @return true if empty + */ + public boolean isEmpty() { + return sp == 0; + } + + /** + * The depth of the lexical context + * @return depth + */ + public int size() { + return sp; + } + + /** + * Pops the innermost block off the context and all nodes that has been contributed + * since it was put there + * + * @param node the node expected to be popped, used to detect unbalanced pushes/pops + * @return the node that was popped + */ + @SuppressWarnings("unchecked") + public <T extends LexicalContextNode> T pop(final T node) { + --sp; + final LexicalContextNode popped = stack[sp]; + stack[sp] = null; + if (popped instanceof Flags) { + return (T)((Flags<?>)popped).setFlag(this, flags[sp]); } - @Override - public FunctionNode next() { - if(next == null) { - throw new NoSuchElementException(); + return (T)popped; + } + + + /** + * Return the top element in the context + * @return the node that was pushed last + */ + public LexicalContextNode peek() { + return stack[sp - 1]; + } + + /** + * Check if a node is in the lexical context + * @param node node to check for + * @return true if in the context + */ + public boolean contains(final LexicalContextNode node) { + for (int i = 0; i < sp; i++) { + if (stack[i] == node) { + return true; } - FunctionNode lnext = next; - next = findNext(); - return lnext; } + return false; + } - private FunctionNode findNext() { - while(it.hasNext()) { - final Block block = it.next(); - if(block instanceof FunctionNode) { - return ((FunctionNode)block); - } + /** + * Replace a node on the lexical context with a new one. Normally + * you should try to engineer IR traversals so this isn't needed + * + * @param oldNode old node + * @param newNode new node + * @return the new node + */ + public LexicalContextNode replace(final LexicalContextNode oldNode, final LexicalContextNode newNode) { + //System.err.println("REPLACE old=" + Debug.id(oldNode) + " new=" + Debug.id(newNode)); + for (int i = sp - 1; i >= 0; i--) { + if (stack[i] == oldNode) { + assert i == (sp - 1) : "violation of contract - we always expect to find the replacement node on top of the lexical context stack: " + newNode + " has " + stack[i + 1].getClass() + " above it"; + stack[i] = newNode; + break; } - return null; - } + } + return newNode; + } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } + /** + * Returns an iterator over all blocks in the context, with the top block (innermost lexical context) first. + * @return an iterator over all blocks in the context. + */ + public Iterator<Block> getBlocks() { + return new NodeIterator<>(Block.class); + } + + /** + * Returns an iterator over all functions in the context, with the top (innermost open) function first. + * @return an iterator over all functions in the context. + */ + public Iterator<FunctionNode> getFunctions() { + return new NodeIterator<>(FunctionNode.class); + } + + /** + * Get the parent block for the current lexical context block + * @return parent block + */ + public Block getParentBlock() { + final Iterator<Block> iter = new NodeIterator<>(Block.class, getCurrentFunction()); + iter.next(); + return iter.hasNext() ? iter.next() : null; } /** @@ -98,12 +252,12 @@ public class LexicalContext implements Cloneable { * @param block the block whose ancestors are returned * @return an iterator over all ancestors block of the given block. */ - public Iterator<Block> getAncestorBlocks(Block block) { - final Iterator<Block> it = getBlocks(); - while(it.hasNext()) { - final Block b = it.next(); - if(block == b) { - return it; + public Iterator<Block> getAncestorBlocks(final Block block) { + final Iterator<Block> iter = getBlocks(); + while (iter.hasNext()) { + final Block b = iter.next(); + if (block == b) { + return iter; } } throw new AssertionError("Block is not on the current lexical context stack"); @@ -115,17 +269,17 @@ public class LexicalContext implements Cloneable { * @return an iterator over a block and all its ancestors. */ public Iterator<Block> getBlocks(final Block block) { - final Iterator<Block> it = getAncestorBlocks(block); + final Iterator<Block> iter = getAncestorBlocks(block); return new Iterator<Block>() { boolean blockReturned = false; @Override public boolean hasNext() { - return it.hasNext() || !blockReturned; + return iter.hasNext() || !blockReturned; } @Override public Block next() { - if(blockReturned) { - return it.next(); + if (blockReturned) { + return iter.next(); } blockReturned = true; return block; @@ -138,61 +292,319 @@ public class LexicalContext implements Cloneable { } /** - * Returns the closest function node to the block. If the block is itself a function, it is returned. - * @param block the block - * @return the function closest to the block. - * @see #getParentFunction(Block) + * Get the function for this block. If the block is itself a function + * this returns identity + * @param block block for which to get function + * @return function for block */ - public FunctionNode getFunction(Block block) { - if(block instanceof FunctionNode) { - return (FunctionNode)block; + public FunctionNode getFunction(final Block block) { + final Iterator<LexicalContextNode> iter = new NodeIterator<>(LexicalContextNode.class); + while (iter.hasNext()) { + final LexicalContextNode next = iter.next(); + if (next == block) { + while (iter.hasNext()) { + final LexicalContextNode next2 = iter.next(); + if (next2 instanceof FunctionNode) { + return (FunctionNode)next2; + } + } + } } - return getParentFunction(block); + assert false; + return null; } /** - * Returns the closest function node to the block and all its ancestor functions. If the block is itself a function, - * it is returned too. - * @param block the block - * @return the closest function node to the block and all its ancestor functions. + * Returns the innermost block in the context. + * @return the innermost block in the context. */ - public Iterator<FunctionNode> getFunctions(final Block block) { - return new FunctionIterator(getBlocks(block)); + public Block getCurrentBlock() { + return getBlocks().next(); } /** - * Returns the containing function of the block. If the block is itself a function, its parent function is returned. - * @param block the block - * @return the containing function of the block. - * @see #getFunction(Block) + * Returns the innermost function in the context. + * @return the innermost function in the context. */ - public FunctionNode getParentFunction(Block block) { - return getFirstFunction(getAncestorBlocks(block)); + public FunctionNode getCurrentFunction() { + if (isEmpty()) { + return null; + } + return new NodeIterator<>(FunctionNode.class).next(); } - private static FunctionNode getFirstFunction(Iterator<Block> it) { - while(it.hasNext()) { - final Block ancestor = it.next(); - if(ancestor instanceof FunctionNode) { - return (FunctionNode)ancestor; + /** + * Get the block in which a symbol is defined + * @param symbol symbol + * @return block in which the symbol is defined, assert if no such block in context + */ + public Block getDefiningBlock(final Symbol symbol) { + if (symbol.isTemp()) { + return null; + } + final String name = symbol.getName(); + for (final Iterator<Block> it = getBlocks(); it.hasNext();) { + final Block next = it.next(); + if (next.getExistingSymbol(name) == symbol) { + return next; } } + throw new AssertionError("Couldn't find symbol " + name + " in the context"); + } + + /** + * Get the function in which a symbol is defined + * @param symbol symbol + * @return function node in which this symbol is defined, assert if no such symbol exists in context + */ + public FunctionNode getDefiningFunction(Symbol symbol) { + if (symbol.isTemp()) { + return null; + } + final String name = symbol.getName(); + for (final Iterator<LexicalContextNode> iter = new NodeIterator<>(LexicalContextNode.class); iter.hasNext();) { + final LexicalContextNode next = iter.next(); + if (next instanceof Block && ((Block)next).getExistingSymbol(name) == symbol) { + while (iter.hasNext()) { + final LexicalContextNode next2 = iter.next(); + if (next2 instanceof FunctionNode) { + return ((FunctionNode)next2); + } + } + throw new AssertionError("Defining block for symbol " + name + " has no function in the context"); + } + } + throw new AssertionError("Couldn't find symbol " + name + " in the context"); + } + + /** + * Is the topmost lexical context element a function body? + * @return true if function body + */ + public boolean isFunctionBody() { + return getParentBlock() == null; + } + + /** + * Returns true if the expression defining the function is a callee of a CallNode that should be the second + * element on the stack, e.g. <code>(function(){})()</code>. That is, if the stack ends with + * {@code [..., CallNode, FunctionNode]} then {@code callNode.getFunction()} should be equal to + * {@code functionNode}, and the top of the stack should itself be a variant of {@code functionNode}. + * @param functionNode the function node being tested + * @return true if the expression defining the current function is a callee of a call expression. + */ + public boolean isFunctionDefinedInCurrentCall(FunctionNode functionNode) { + final LexicalContextNode parent = stack[sp - 2]; + if(parent instanceof CallNode && ((CallNode)parent).getFunction() == functionNode) { + assert functionNode.getSource() == peek().getSource(); + return true; + } + return false; + } + + /** + * Get the parent function for a function in the lexical context + * @param functionNode function for which to get parent + * @return parent function of functionNode or null if none (e.g. if functionNode is the program) + */ + public FunctionNode getParentFunction(final FunctionNode functionNode) { + final Iterator<FunctionNode> iter = new NodeIterator<>(FunctionNode.class); + while (iter.hasNext()) { + final FunctionNode next = iter.next(); + if (next == functionNode) { + return iter.hasNext() ? iter.next() : null; + } + } + assert false; return null; } /** - * Returns the innermost block in the context. - * @return the innermost block in the context. + * Count the number of with scopes until a given node + * @param until node to stop counting at, or null if all nodes should be counted + * @return number of with scopes encountered in the context */ - public Block getCurrentBlock() { - return lexicalContext.element(); + public int getScopeNestingLevelTo(final LexicalContextNode until) { + //count the number of with nodes until "until" is hit + int n = 0; + for (final Iterator<WithNode> iter = new NodeIterator<>(WithNode.class, until); iter.hasNext(); iter.next()) { + n++; + } + return n; + } + + private BreakableNode getBreakable() { + for (final NodeIterator<BreakableNode> iter = new NodeIterator<>(BreakableNode.class, getCurrentFunction()); iter.hasNext(); ) { + final BreakableNode next = iter.next(); + if (next.isBreakableWithoutLabel()) { + return next; + } + } + return null; } /** - * Returns the innermost function in the context. - * @return the innermost function in the context. + * Find the breakable node corresponding to this label. + * @param label label to search for, if null the closest breakable node will be returned unconditionally, e.g. a while loop with no label + * @return closest breakable node */ - public FunctionNode getCurrentFunction() { - return getFirstFunction(getBlocks()); + public BreakableNode getBreakable(final IdentNode label) { + if (label != null) { + final LabelNode foundLabel = findLabel(label.getName()); + if (foundLabel != null) { + // iterate to the nearest breakable to the foundLabel + BreakableNode breakable = null; + for (final NodeIterator<BreakableNode> iter = new NodeIterator<>(BreakableNode.class, foundLabel); iter.hasNext(); ) { + breakable = iter.next(); + } + return breakable; + } + return null; + } + return getBreakable(); + } + + private LoopNode getContinueTo() { + final Iterator<LoopNode> iter = new NodeIterator<>(LoopNode.class, getCurrentFunction()); + return iter.hasNext() ? iter.next() : null; + } + + /** + * Find the continue target node corresponding to this label. + * @param label label to search for, if null the closest loop node will be returned unconditionally, e.g. a while loop with no label + * @return closest continue target node + */ + public LoopNode getContinueTo(final IdentNode label) { + if (label != null) { + final LabelNode foundLabel = findLabel(label.getName()); + if (foundLabel != null) { + // iterate to the nearest loop to the foundLabel + LoopNode loop = null; + for (final NodeIterator<LoopNode> iter = new NodeIterator<>(LoopNode.class, foundLabel); iter.hasNext(); ) { + loop = iter.next(); + } + return loop; + } + return null; + } + return getContinueTo(); + } + + /** + * Check the lexical context for a given label node by name + * @param name name of the label + * @return LabelNode if found, null otherwise + */ + public LabelNode findLabel(final String name) { + for (final Iterator<LabelNode> iter = new NodeIterator<>(LabelNode.class, getCurrentFunction()); iter.hasNext(); ) { + final LabelNode next = iter.next(); + if (next.getLabel().getName().equals(name)) { + return next; + } + } + return null; + } + + /** + * Checks whether a given label is a jump destination that lies outside a given + * split node + * @param splitNode the split node + * @param label the label + * @return true if label resides outside the split node + */ + public boolean isExternalTarget(final SplitNode splitNode, final Label label) { + boolean targetFound = false; + for (int i = sp - 1; i >= 0; i--) { + final LexicalContextNode next = stack[i]; + if (next == splitNode) { + return !targetFound; + } + + if (next instanceof BreakableNode) { + for (final Label l : ((BreakableNode)next).getLabels()) { + if (l == label) { + targetFound = true; + break; + } + } + } + } + assert false : label + " was expected in lexical context " + LexicalContext.this + " but wasn't"; + return false; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer(); + sb.append("[ "); + for (int i = 0; i < sp; i++) { + final Node node = stack[i]; + sb.append(node.getClass().getSimpleName()); + sb.append('@'); + sb.append(Debug.id(node)); + sb.append(':'); + final Source source = node.getSource(); + String src = source.toString(); + if (src.indexOf(File.pathSeparator) != -1) { + src = src.substring(src.lastIndexOf(File.pathSeparator)); + } + src += ' '; + src += source.getLine(node.getStart()); + sb.append(' '); + } + sb.append(" ==> ]"); + return sb.toString(); + } + + private class NodeIterator <T extends LexicalContextNode> implements Iterator<T> { + private int index; + private T next; + private final Class<T> clazz; + private LexicalContextNode until; + + NodeIterator(final Class<T> clazz) { + this(clazz, null); + } + + NodeIterator(final Class<T> clazz, final LexicalContextNode until) { + this.index = sp - 1; + this.clazz = clazz; + this.until = until; + this.next = findNext(); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public T next() { + if (next == null) { + throw new NoSuchElementException(); + } + T lnext = next; + next = findNext(); + return lnext; + } + + private T findNext() { + for (int i = index; i >= 0; i--) { + final Node node = stack[i]; + if (node == until) { + return null; + } + if (clazz.isAssignableFrom(node.getClass())) { + index = i - 1; + return clazz.cast(node); + } + } + return null; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } } } diff --git a/src/jdk/nashorn/internal/ir/DoWhileNode.java b/src/jdk/nashorn/internal/ir/LexicalContextNode.java index 3939795e..e48c6e0b 100644 --- a/src/jdk/nashorn/internal/ir/DoWhileNode.java +++ b/src/jdk/nashorn/internal/ir/LexicalContextNode.java @@ -22,61 +22,52 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package jdk.nashorn.internal.ir; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** - * Loop representing do while loops. This is mostly split from WhileNode - * because of the different order of the Phi Traversals - * + * Superclass for nodes that can be part of the lexical context + * @see LexicalContext */ -public class DoWhileNode extends WhileNode { - +public abstract class LexicalContextNode extends Node { /** * Constructor * - * @param source the source - * @param token token - * @param finish finish + * @param source source + * @param token token + * @param finish finish */ - public DoWhileNode(final Source source, final long token, final int finish) { + protected LexicalContextNode(final Source source, final long token, final int finish) { super(source, token, finish); } /** * Copy constructor * - * @param doWhileNode source node - * @param cs copy state + * @param node source node */ - protected DoWhileNode(final DoWhileNode doWhileNode, final CopyState cs) { - super(doWhileNode, cs); + protected LexicalContextNode(final LexicalContextNode node) { + super(node); } - @Override - protected Node copy(final CopyState cs) { - return new DoWhileNode(this, cs); - } + /** + * Accept function for the node given a lexical context. It must be prepared + * to replace itself if present in the lexical context + * + * @param lc lexical context + * @param visitor node visitor + * + * @return new node or same node depending on state change + */ + protected abstract Node accept(final LexicalContext lc, final NodeVisitor visitor); @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterDoWhileNode(this) != null) { - body = (Block)body.accept(visitor); - test = test.accept(visitor); - - return visitor.leaveDoWhileNode(this); - } - - return this; - } - - @Override - public void toString(final StringBuilder sb) { - sb.append("while ("); - test.toString(sb); - sb.append(')'); + final LexicalContext lc = visitor.getLexicalContext(); + lc.push(this); + final LexicalContextNode newNode = (LexicalContextNode)accept(lc, visitor); + return lc.pop(newNode); } } diff --git a/src/jdk/nashorn/internal/ir/LineNumberNode.java b/src/jdk/nashorn/internal/ir/LineNumberNode.java index c7912ff0..63f04594 100644 --- a/src/jdk/nashorn/internal/ir/LineNumberNode.java +++ b/src/jdk/nashorn/internal/ir/LineNumberNode.java @@ -25,6 +25,7 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.parser.Token; import jdk.nashorn.internal.runtime.Source; @@ -32,8 +33,8 @@ import jdk.nashorn.internal.runtime.Source; /** * IR Node representing a line number */ - -public class LineNumberNode extends Node { +@Immutable +public final class LineNumberNode extends Node { /** Line number */ private final int lineNumber; @@ -46,24 +47,17 @@ public class LineNumberNode extends Node { */ public LineNumberNode(final Source source, final long token, final int lineNumber) { super(source, token, Token.descPosition(token)); - this.lineNumber = lineNumber; } - private LineNumberNode(final LineNumberNode lineNumberNode) { + private LineNumberNode(final LineNumberNode lineNumberNode) { super(lineNumberNode); - this.lineNumber = lineNumberNode.getLineNumber(); } @Override - protected Node copy(final CopyState cs) { - return new LineNumberNode(this); - } - - @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterLineNumberNode(this) != null) { + if (visitor.enterLineNumberNode(this)) { return visitor.leaveLineNumberNode(this); } diff --git a/src/jdk/nashorn/internal/ir/LiteralNode.java b/src/jdk/nashorn/internal/ir/LiteralNode.java index cc424b7a..ae80214c 100644 --- a/src/jdk/nashorn/internal/ir/LiteralNode.java +++ b/src/jdk/nashorn/internal/ir/LiteralNode.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.List; import jdk.nashorn.internal.codegen.CompileUnit; 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.Lexer.LexerToken; import jdk.nashorn.internal.parser.Token; @@ -44,6 +45,7 @@ import jdk.nashorn.internal.runtime.Undefined; * * @param <T> the literal type */ +@Immutable public abstract class LiteralNode<T> extends Node implements PropertyKey { /** Literal value */ protected final T value; @@ -93,23 +95,6 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { return value == null; } - @Override - public int hashCode() { - return value == null ? 0 : value.hashCode(); - } - - @Override - public boolean equals(final Object other) { - if (!(other instanceof LiteralNode<?>)) { - return false; - } - final LiteralNode<?> otherNode = (LiteralNode<?>)other; - if (otherNode.isNull()) { - return isNull(); - } - return ((LiteralNode<?>)other).getValue().equals(value); - } - /** * Check if the literal value is boolean true * @return true if literal value is boolean true @@ -226,7 +211,7 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterLiteralNode(this) != null) { + if (visitor.enterLiteralNode(this)) { return visitor.leaveLiteralNode(this); } @@ -274,7 +259,8 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { return new NodeLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish()); } - private static class BooleanLiteralNode extends LiteralNode<Boolean> { + @Immutable + private static final class BooleanLiteralNode extends LiteralNode<Boolean> { private BooleanLiteralNode(final Source source, final long token, final int finish, final boolean value) { super(source, Token.recast(token, value ? TokenType.TRUE : TokenType.FALSE), finish, value); @@ -285,11 +271,6 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { } @Override - protected Node copy(final CopyState cs) { - return new BooleanLiteralNode(this); - } - - @Override public boolean isTrue() { return value; } @@ -331,7 +312,8 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { return new BooleanLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish(), value); } - private static class NumberLiteralNode extends LiteralNode<Number> { + @Immutable + private static final class NumberLiteralNode extends LiteralNode<Number> { private final Type type = numberGetType(value); @@ -358,11 +340,6 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { } @Override - protected Node copy(final CopyState cs) { - return new NumberLiteralNode(this); - } - - @Override public Type getType() { return type; } @@ -407,11 +384,6 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { private UndefinedLiteralNode(final UndefinedLiteralNode literalNode) { super(literalNode); } - - @Override - protected Node copy(final CopyState cs) { - return new UndefinedLiteralNode(this); - } } /** @@ -440,6 +412,7 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { return new UndefinedLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish()); } + @Immutable private static class StringLiteralNode extends LiteralNode<String> { private StringLiteralNode(final Source source, final long token, final int finish, final String value) { super(source, Token.recast(token, TokenType.STRING), finish, value); @@ -450,11 +423,6 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { } @Override - protected Node copy(final CopyState cs) { - return new StringLiteralNode(this); - } - - @Override public void toString(final StringBuilder sb) { sb.append('\"'); sb.append(value); @@ -488,6 +456,7 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { return new StringLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish(), value); } + @Immutable private static class LexerTokenLiteralNode extends LiteralNode<LexerToken> { private LexerTokenLiteralNode(final Source source, final long token, final int finish, final LexerToken value) { super(source, Token.recast(token, TokenType.STRING), finish, value); //TODO is string the correct token type here? @@ -498,11 +467,6 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { } @Override - protected Node copy(final CopyState cs) { - return new LexerTokenLiteralNode(this); - } - - @Override public Type getType() { return Type.OBJECT; } @@ -539,7 +503,7 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { return new LexerTokenLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish(), value); } - private static class NodeLiteralNode extends LiteralNode<Node> { + private static final class NodeLiteralNode extends LiteralNode<Node> { private NodeLiteralNode(final Source source, final long token, final int finish) { this(source, token, finish, null); @@ -558,13 +522,8 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { } @Override - protected Node copy(final CopyState cs) { - return new NodeLiteralNode(this); - } - - @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterLiteralNode(this) != null) { + if (visitor.enterLiteralNode(this)) { if (value != null) { final Node newValue = value.accept(visitor); if(value != newValue) { @@ -617,7 +576,7 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { /** * Array literal node class. */ - public static class ArrayLiteralNode extends LiteralNode<Node[]> { + public static final class ArrayLiteralNode extends LiteralNode<Node[]> { private static class PostsetMarker { //empty } @@ -705,11 +664,6 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { this.elementType = node.elementType; } - @Override - protected Node copy(final CopyState cs) { - return new ArrayLiteralNode(this); - } - /** * Compute things like widest element type needed. Internal use from compiler only */ @@ -894,7 +848,7 @@ public abstract class LiteralNode<T> extends Node implements PropertyKey { @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterLiteralNode(this) != null) { + if (visitor.enterLiteralNode(this)) { for (int i = 0; i < value.length; i++) { final Node element = value[i]; if (element != null) { diff --git a/src/jdk/nashorn/internal/ir/Location.java b/src/jdk/nashorn/internal/ir/Location.java index c8e01dd3..cd9edb5b 100644 --- a/src/jdk/nashorn/internal/ir/Location.java +++ b/src/jdk/nashorn/internal/ir/Location.java @@ -25,16 +25,13 @@ package jdk.nashorn.internal.ir; -import java.util.Objects; import jdk.nashorn.internal.parser.Token; import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.Source; /** * Used to locate an entity back to it's source file. - * */ - public class Location implements Cloneable { /** Source of entity. */ private final Source source; @@ -73,22 +70,13 @@ public class Location implements Cloneable { } @Override - public boolean equals(final Object other) { - if (other == null) { - return false; - } - - if (other.getClass() != this.getClass()) { - return false; - } - - final Location loc = (Location)other; - return token == loc.token && Objects.equals(source, loc.source); + public final boolean equals(final Object other) { + return super.equals(other); } @Override - public int hashCode() { - return Token.hashCode(token) ^ Objects.hashCode(source); + public final int hashCode() { + return super.hashCode(); } /** diff --git a/src/jdk/nashorn/internal/ir/LoopNode.java b/src/jdk/nashorn/internal/ir/LoopNode.java new file mode 100644 index 00000000..b3909dc2 --- /dev/null +++ b/src/jdk/nashorn/internal/ir/LoopNode.java @@ -0,0 +1,176 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.ir; + +import java.util.Arrays; +import java.util.List; + +import jdk.nashorn.internal.codegen.Label; +import jdk.nashorn.internal.runtime.Source; + +/** + * A loop node, for example a while node, do while node or for node + */ +public abstract class LoopNode extends BreakableNode { + /** loop continue label. */ + protected final Label continueLabel; + + /** Loop test node, null if infinite */ + protected final Node test; + + /** Loop body */ + protected final Block body; + + /** Can control flow escape from loop, e.g. through breaks or continues to outer loops? */ + protected final boolean controlFlowEscapes; + + /** + * Constructor + * + * @param source source + * @param token token + * @param finish finish + * @param test test, or null if infinite loop + * @param body loop body + * @param controlFlowEscapes controlFlowEscapes + */ + protected LoopNode(final Source source, final long token, final int finish, final Node test, final Block body, final boolean controlFlowEscapes) { + super(source, token, finish, new Label("while_break")); + this.continueLabel = new Label("while_continue"); + this.test = test; + this.body = body; + this.controlFlowEscapes = controlFlowEscapes; + } + + /** + * Constructor + * + * @param loopNode loop node + * @param test new test + * @param body new body + * @param controlFlowEscapes controlFlowEscapes + */ + protected LoopNode(final LoopNode loopNode, final Node test, final Block body, final boolean controlFlowEscapes) { + super(loopNode); + this.continueLabel = new Label(loopNode.continueLabel); + this.test = test; + this.body = body; + this.controlFlowEscapes = controlFlowEscapes; + } + + @Override + public abstract Node ensureUniqueLabels(final LexicalContext lc); + + /** + * Does the control flow escape from this loop, i.e. through breaks or + * continues to outer loops? + * @return true if control flow escapes + */ + public boolean controlFlowEscapes() { + return controlFlowEscapes; + } + + + @Override + public boolean isTerminal() { + if (!mustEnter()) { + return false; + } + //must enter but control flow may escape - then not terminal + if (controlFlowEscapes) { + return false; + } + //must enter, but body ends with return - then terminal + if (body.isTerminal()) { + return true; + } + //no breaks or returns, it is still terminal if we can never exit + return test == null; + } + + /** + * Conservative check: does this loop have to be entered? + * @return true if body will execute at least once + */ + public abstract boolean mustEnter(); + + /** + * Get the continue label for this while node, i.e. location to go to on continue + * @return continue label + */ + public Label getContinueLabel() { + return continueLabel; + } + + @Override + public List<Label> getLabels() { + return Arrays.asList(breakLabel, continueLabel); + } + + @Override + public boolean isLoop() { + return true; + } + + /** + * Get the body for this for node + * @return the body + */ + public abstract Block getBody(); + + /** + * @param lc lexical context + * @param body new body + * @return new for node if changed or existing if not + */ + public abstract LoopNode setBody(final LexicalContext lc, final Block body); + + /** + * Get the test for this for node + * @return the test + */ + public abstract Node getTest(); + + /** + * Set the test for this for node + * + * @param lc lexical context + * @param test new test + * @return same or new node depending on if test was changed + */ + public abstract LoopNode setTest(final LexicalContext lc, final Node test); + + /** + * Set the control flow escapes flag for this node. + * TODO - integrate this with Lowering in a better way + * + * @param lc lexical context + * @param controlFlowEscapes control flow escapes value + * @return new loop node if changed otherwise the same + */ + public abstract LoopNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes); + +} diff --git a/src/jdk/nashorn/internal/ir/Node.java b/src/jdk/nashorn/internal/ir/Node.java index c5f01337..dfed903d 100644 --- a/src/jdk/nashorn/internal/ir/Node.java +++ b/src/jdk/nashorn/internal/ir/Node.java @@ -25,7 +25,9 @@ package jdk.nashorn.internal.ir; -import java.util.IdentityHashMap; +import java.util.ArrayList; +import java.util.List; + import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.parser.Token; @@ -33,30 +35,17 @@ import jdk.nashorn.internal.runtime.Source; /** * Nodes are used to compose Abstract Syntax Trees. - * */ public abstract class Node extends Location { /** Node symbol. */ private Symbol symbol; /** Start of source range. */ - protected int start; + protected final int start; /** End of source range. */ protected int finish; - /** Has this node been resolved - i.e. emitted code already */ - private boolean isResolved; - - /** Is this node terminal */ - private boolean isTerminal; - - /** Is this a goto node */ - private boolean hasGoto; - - /** Is this a discard */ - private boolean shouldDiscard; - /** * Constructor * @@ -72,6 +61,21 @@ public abstract class Node extends Location { } /** + * Constructor + * + * @param source source + * @param token token + * @param start start + * @param finish finish + */ + protected Node(final Source source, final long token, final int start, final int finish) { + super(source, token); + + this.start = start; + this.finish = finish; + } + + /** * Copy constructor * * @param node source node @@ -79,13 +83,9 @@ public abstract class Node extends Location { protected Node(final Node node) { super(node); - this.symbol = node.symbol; - this.isResolved = node.isResolved; - this.isTerminal = node.isTerminal; - this.hasGoto = node.hasGoto; - this.shouldDiscard = node.shouldDiscard; - this.start = node.start; - this.finish = node.finish; + this.symbol = node.symbol; + this.start = node.start; + this.finish = node.finish; } /** @@ -156,28 +156,6 @@ public abstract class Node extends Location { } /** - * Test to see if code been generated for this node. Set isResolved if not. - * - * @return True if node has already been resolved. - */ - public boolean testResolved() { - if (isResolved()) { - return true; - } - - setIsResolved(true); - - return false; - } - - /** - * Reset the resolved flag. - */ - public void resetResolved() { - setIsResolved(false); - } - - /** * Is this a debug info node like LineNumberNode etc? * * @return true if this is a debug node @@ -187,72 +165,13 @@ public abstract class Node extends Location { } /** - * Helper class used for node cloning - */ - public static final class CopyState { - private final IdentityHashMap<Node, Node> cloneMap = new IdentityHashMap<>(); - - /** - * Find existing or create new copy of the node. - * - * @param node Node to copy. - * - * @return New object. - */ - public Node existingOrCopy(final Node node) { - if (node != null) { - Node copy = cloneMap.get(node); - - if (copy == null) { - copy = node.copy(this); - cloneMap.put(node, copy); - } - - return copy; - } - - return node; - } - - /** - * Find existing or use old copy of the node. - * - * @param node Node to copy. - * - * @return new object. - */ - public Node existingOrSame(final Node node) { - if (node != null) { - Node copy = cloneMap.get(node); - - if (copy == null) { - copy = node; - } - - return copy; - } - - return node; - } - } - - /** - * Deep copy the node. - * - * @return Deep copy of the Node. - */ - public final Node copy() { - return copy(new CopyState()); - } - - /** - * Deep copy the node. - * - * @param cs CopyState passed around to re-use certain nodes. - * @return Deep copy of the Node. + * For reference copies - ensure that labels in the copy node are unique + * using an appropriate copy constructor + * @param lc lexical context + * @return new node or same of no labels */ - protected Node copy(final CopyState cs) { - return cs.existingOrCopy(this); + public Node ensureUniqueLabels(final LexicalContext lc) { + return this; } /** @@ -283,35 +202,7 @@ public abstract class Node extends Location { * @return true if terminal */ public boolean hasTerminalFlags() { - return isTerminal || hasGoto; - } - - /** - * Copy the terminal flags state of a node to another node - * - * @param other source node - */ - public void copyTerminalFlags(final Node other) { - isTerminal = other.isTerminal; - hasGoto = other.hasGoto; - } - - /** - * Check if the return value of this expression should be discarded - * @return true if return value is discarded - */ - public boolean shouldDiscard() { - return shouldDiscard; - } - - /** - * Setter that determines whether this node's return value should be discarded - * or not - * - * @param shouldDiscard true if return value is discarded, false otherwise - */ - public void setDiscard(final boolean shouldDiscard) { - this.shouldDiscard = shouldDiscard; + return isTerminal() || hasGoto(); } /** @@ -336,29 +227,7 @@ public abstract class Node extends Location { * @return true if node has goto semantics */ public boolean hasGoto() { - return hasGoto; - } - - /** - * Flag this node as having goto semantics as described in {@link Node#hasGoto()} - */ - public void setHasGoto() { - this.hasGoto = true; - } - - /** - * Check whether this node is resolved, i.e. code has been generated for it - * @return true if node is resolved - */ - public boolean isResolved() { - return isResolved; - } - - /** - * Flag this node as resolved or not, i.e. code has been generated for it - */ - private void setIsResolved(boolean isResolved) { - this.isResolved = isResolved; + return false; } /** @@ -370,14 +239,6 @@ public abstract class Node extends Location { } /** - * Set start position for node - * @param start start position - */ - public void setStart(final int start) { - this.start = start; - } - - /** * Return the Symbol the compiler has assigned to this Node. The symbol * is the place where it's expression value is stored after evaluation * @@ -404,17 +265,29 @@ public abstract class Node extends Location { * @return true if this node is terminal */ public boolean isTerminal() { - return isTerminal; + return false; } - /** - * Set this to be a terminal node, i.e. it terminates control flow as described - * in {@link Node#isTerminal()} - * - * @param isTerminal true if this is a terminal node, false otherwise - */ - public void setIsTerminal(final boolean isTerminal) { - this.isTerminal = isTerminal; + //on change, we have to replace the entire list, that's we can't simple do ListIterator.set + static <T extends Node> List<T> accept(final NodeVisitor visitor, final Class<T> clazz, final List<T> list) { + boolean changed = false; + final List<T> newList = new ArrayList<>(); + + for (final Node node : list) { + final T newNode = clazz.cast(node.accept(visitor)); + if (newNode != node) { + changed = true; + } + newList.add(newNode); + } + + return changed ? newList : list; } + static <T extends LexicalContextNode> T replaceInLexicalContext(final LexicalContext lc, final T oldNode, final T newNode) { + if (lc != null) { + lc.replace(oldNode, newNode); + } + return newNode; + } } diff --git a/src/jdk/nashorn/internal/ir/ObjectNode.java b/src/jdk/nashorn/internal/ir/ObjectNode.java index f6724a62..744a44d7 100644 --- a/src/jdk/nashorn/internal/ir/ObjectNode.java +++ b/src/jdk/nashorn/internal/ir/ObjectNode.java @@ -25,16 +25,18 @@ package jdk.nashorn.internal.ir; -import java.util.ArrayList; import java.util.Collections; import java.util.List; + +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation of an object literal. */ -public class ObjectNode extends Node { +@Immutable +public final class ObjectNode extends Node { /** Literal elements. */ private final List<Node> elements; @@ -49,35 +51,18 @@ public class ObjectNode extends Node { */ public ObjectNode(final Source source, final long token, final int finish, final List<Node> elements) { super(source, token, finish); - this.elements = elements; } - private ObjectNode(final ObjectNode objectNode, final CopyState cs) { + private ObjectNode(final ObjectNode objectNode, final List<Node> elements) { super(objectNode); - - final List<Node> newElements = new ArrayList<>(); - - for (final Node element : objectNode.elements) { - newElements.add(cs.existingOrCopy(element)); - } - - this.elements = newElements; - } - - @Override - protected Node copy(final CopyState cs) { - return new ObjectNode(this, cs); + this.elements = elements; } @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterObjectNode(this) != null) { - for (int i = 0, count = elements.size(); i < count; i++) { - elements.set(i, elements.get(i).accept(visitor)); - } - - return visitor.leaveObjectNode(this); + if (visitor.enterObjectNode(this)) { + return visitor.leaveObjectNode(setElements(Node.accept(visitor, Node.class, elements))); } return this; @@ -112,4 +97,11 @@ public class ObjectNode extends Node { public List<Node> getElements() { return Collections.unmodifiableList(elements); } + + private ObjectNode setElements(final List<Node> elements) { + if (this.elements == elements) { + return this; + } + return new ObjectNode(this, elements); + } } diff --git a/src/jdk/nashorn/internal/ir/PropertyNode.java b/src/jdk/nashorn/internal/ir/PropertyNode.java index a6bc49de..635e1aa3 100644 --- a/src/jdk/nashorn/internal/ir/PropertyNode.java +++ b/src/jdk/nashorn/internal/ir/PropertyNode.java @@ -25,28 +25,27 @@ package jdk.nashorn.internal.ir; -import jdk.nashorn.internal.ir.annotations.Reference; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation of an object literal property. */ -public class PropertyNode extends Node { +@Immutable +public final class PropertyNode extends Node { /** Property key. */ - private PropertyKey key; + private final PropertyKey key; /** Property value. */ - private Node value; + private final Node value; /** Property getter. */ - @Reference - private Node getter; + private final FunctionNode getter; /** Property getter. */ - @Reference - private Node setter; + private final FunctionNode setter; /** * Constructor @@ -56,26 +55,23 @@ public class PropertyNode extends Node { * @param finish finish * @param key the key of this property * @param value the value of this property + * @param getter getter function body + * @param setter setter function body */ - public PropertyNode(final Source source, final long token, final int finish, final PropertyKey key, final Node value) { + public PropertyNode(final Source source, final long token, final int finish, final PropertyKey key, final Node value, final FunctionNode getter, final FunctionNode setter) { super(source, token, finish); - this.key = key; this.value = value; + this.getter = getter; + this.setter = setter; } - private PropertyNode(final PropertyNode propertyNode, final CopyState cs) { + private PropertyNode(final PropertyNode propertyNode, final PropertyKey key, final Node value, final FunctionNode getter, final FunctionNode setter) { super(propertyNode); - - this.key = (PropertyKey)cs.existingOrCopy((Node)propertyNode.key); - this.value = cs.existingOrCopy(propertyNode.value); - this.getter = cs.existingOrSame(propertyNode.getter); - this.setter = cs.existingOrSame(propertyNode.setter); - } - - @Override - protected Node copy(final CopyState cs) { - return new PropertyNode(this, cs); + this.key = key; + this.value = value; + this.getter = getter; + this.setter = setter; } /** @@ -88,22 +84,12 @@ public class PropertyNode extends Node { @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterPropertyNode(this) != null) { - key = (PropertyKey)((Node)key).accept(visitor); - - if (value != null) { - value = value.accept(visitor); - } - - if (getter != null) { - getter = getter.accept(visitor); - } - - if (setter != null) { - setter = setter.accept(visitor); - } - - return visitor.leavePropertyNode(this); + if (visitor.enterPropertyNode(this)) { + return visitor.leavePropertyNode( + setKey((PropertyKey)((Node)key).accept(visitor)). + setValue(value == null ? null : value.accept(visitor)). + setGetter(getter == null ? null : (FunctionNode)getter.accept(visitor)). + setSetter(setter == null ? null : (FunctionNode)setter.accept(visitor))); } return this; @@ -136,16 +122,20 @@ public class PropertyNode extends Node { * Get the getter for this property * @return getter or null if none exists */ - public Node getGetter() { + public FunctionNode getGetter() { return getter; } /** * Set the getter of this property, null if none * @param getter getter + * @return same node or new node if state changed */ - public void setGetter(final Node getter) { - this.getter = getter; + public PropertyNode setGetter(final FunctionNode getter) { + if (this.getter == getter) { + return this; + } + return new PropertyNode(this, key, value, getter, setter); } /** @@ -156,20 +146,31 @@ public class PropertyNode extends Node { return (Node)key; } + private PropertyNode setKey(final PropertyKey key) { + if (this.key == key) { + return this; + } + return new PropertyNode(this, key, value, getter, setter); + } + /** * Get the setter for this property * @return setter or null if none exists */ - public Node getSetter() { + public FunctionNode getSetter() { return setter; } /** * Set the setter for this property, null if none * @param setter setter + * @return same node or new node if state changed */ - public void setSetter(final Node setter) { - this.setter = setter; + public PropertyNode setSetter(final FunctionNode setter) { + if (this.setter == setter) { + return this; + } + return new PropertyNode(this, key, value, getter, setter); } /** @@ -183,8 +184,12 @@ public class PropertyNode extends Node { /** * Set the value of this property * @param value new value + * @return same node or new node if state changed */ - public void setValue(final Node value) { - this.value = value; - } + public PropertyNode setValue(final Node value) { + if (this.value == value) { + return this; + } + return new PropertyNode(this, key, value, getter, setter); + } } diff --git a/src/jdk/nashorn/internal/ir/ReturnNode.java b/src/jdk/nashorn/internal/ir/ReturnNode.java index 1400f395..dafc956d 100644 --- a/src/jdk/nashorn/internal/ir/ReturnNode.java +++ b/src/jdk/nashorn/internal/ir/ReturnNode.java @@ -27,22 +27,17 @@ package jdk.nashorn.internal.ir; import static jdk.nashorn.internal.parser.TokenType.RETURN; import static jdk.nashorn.internal.parser.TokenType.YIELD; - -import jdk.nashorn.internal.ir.annotations.Ignore; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for RETURN or YIELD statements. - * */ +@Immutable public class ReturnNode extends Node { /** Optional expression. */ - private Node expression; - - /** Try chain. */ - @Ignore - private final TryNode tryChain; + private final Node expression; /** * Constructor @@ -51,27 +46,20 @@ public class ReturnNode extends Node { * @param token token * @param finish finish * @param expression expression to return - * @param tryChain surrounding try chain. */ - public ReturnNode(final Source source, final long token, final int finish, final Node expression, final TryNode tryChain) { + public ReturnNode(final Source source, final long token, final int finish, final Node expression) { super(source, token, finish); - this.expression = expression; - this.tryChain = tryChain; - - setIsTerminal(true); } - private ReturnNode(final ReturnNode returnNode, final CopyState cs) { + private ReturnNode(final ReturnNode returnNode, final Node expression) { super(returnNode); - - this.expression = cs.existingOrCopy(returnNode.expression); - this.tryChain = (TryNode)cs.existingOrSame(returnNode.tryChain); + this.expression = expression; } @Override - protected Node copy(final CopyState cs) { - return new ReturnNode(this, cs); + public boolean isTerminal() { + return true; } /** @@ -100,11 +88,10 @@ public class ReturnNode extends Node { @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterReturnNode(this) != null) { + if (visitor.enterReturnNode(this)) { if (expression != null) { - expression = expression.accept(visitor); + return visitor.leaveReturnNode(setExpression(expression.accept(visitor))); } - return visitor.leaveReturnNode(this); } @@ -121,25 +108,6 @@ public class ReturnNode extends Node { } } - @Override - public boolean equals(final Object other) { - if (other instanceof ReturnNode) { - final ReturnNode otherReturn = (ReturnNode)other; - if (hasExpression() != otherReturn.hasExpression()) { - return false; - } else if (hasExpression()) { - return otherReturn.getExpression().equals(getExpression()); - } - return true; - } - return false; - } - - @Override - public int hashCode() { - return 0x4711_17 ^ (expression == null ? 0 : expression.hashCode()); - } - /** * Get the expression this node returns * @return return expression, or null if void return @@ -151,16 +119,13 @@ public class ReturnNode extends Node { /** * Reset the expression this node returns * @param expression new expression, or null if void return + * @return new or same return node */ - public void setExpression(final Node expression) { - this.expression = expression; + public ReturnNode setExpression(final Node expression) { + if (this.expression == expression) { + return this; + } + return new ReturnNode(this, expression); } - /** - * Get the surrounding try chain for this return node - * @return try chain - */ - public TryNode getTryChain() { - return tryChain; - } } diff --git a/src/jdk/nashorn/internal/ir/RuntimeNode.java b/src/jdk/nashorn/internal/ir/RuntimeNode.java index 461007cd..7bdb6c0a 100644 --- a/src/jdk/nashorn/internal/ir/RuntimeNode.java +++ b/src/jdk/nashorn/internal/ir/RuntimeNode.java @@ -30,14 +30,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; 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.TokenType; import jdk.nashorn.internal.runtime.Source; /** * IR representation for a runtime call. - * */ +@Immutable public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { /** @@ -271,10 +272,10 @@ public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { private final List<Node> args; /** Call site override - e.g. we know that a ScriptRuntime.ADD will return an int */ - private Type callSiteType; + private final Type callSiteType; /** is final - i.e. may not be removed again, lower in the code pipeline */ - private boolean isFinal; + private final boolean isFinal; /** * Constructor @@ -290,6 +291,17 @@ public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { this.request = request; this.args = args; + this.callSiteType = null; + this.isFinal = false; + } + + private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final Type callSiteType, final boolean isFinal, final List<Node> args) { + super(runtimeNode); + + this.request = request; + this.args = args; + this.callSiteType = callSiteType; + this.isFinal = isFinal; } /** @@ -326,8 +338,10 @@ public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { public RuntimeNode(final Node parent, final Request request, final List<Node> args) { super(parent); - this.request = request; - this.args = args; + this.request = request; + this.args = args; + this.callSiteType = null; + this.isFinal = false; } /** @@ -350,20 +364,6 @@ public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { this(parent, request, parent.lhs(), parent.rhs()); } - private RuntimeNode(final RuntimeNode runtimeNode, final CopyState cs) { - super(runtimeNode); - - final List<Node> newArgs = new ArrayList<>(); - - for (final Node arg : runtimeNode.args) { - newArgs.add(cs.existingOrCopy(arg)); - } - - this.request = runtimeNode.request; - this.args = newArgs; - this.callSiteType = runtimeNode.callSiteType; - } - /** * Is this node final - i.e. it can never be replaced with other nodes again * @return true if final @@ -374,14 +374,14 @@ public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { /** * Flag this node as final - i.e it may never be replaced with other nodes again + * @param isFinal is the node final, i.e. can not be removed and replaced by a less generic one later in codegen + * @return same runtime node if already final, otherwise a new one */ - public void setIsFinal() { - this.isFinal = true; - } - - @Override - protected Node copy(final CopyState cs) { - return new RuntimeNode(this, cs); + public RuntimeNode setIsFinal(final boolean isFinal) { + if (this.isFinal == isFinal) { + return this; + } + return new RuntimeNode(this, request, callSiteType, isFinal, args); } /** @@ -394,8 +394,10 @@ public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { @Override public RuntimeNode setType(final Type type) { - this.callSiteType = type; - return this; + if (this.callSiteType == type) { + return this; + } + return new RuntimeNode(this, request, type, isFinal, args); } @Override @@ -409,12 +411,12 @@ public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterRuntimeNode(this) != null) { - for (int i = 0, count = args.size(); i < count; i++) { - args.set(i, args.get(i).accept(visitor)); + if (visitor.enterRuntimeNode(this)) { + final List<Node> newArgs = new ArrayList<>(); + for (final Node arg : args) { + newArgs.add(arg.accept(visitor)); } - - return visitor.leaveRuntimeNode(this); + return visitor.leaveRuntimeNode(setArgs(newArgs)); } return this; @@ -449,6 +451,13 @@ public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> { return Collections.unmodifiableList(args); } + private RuntimeNode setArgs(final List<Node> args) { + if (this.args == args) { + return this; + } + return new RuntimeNode(this, request, callSiteType, isFinal, args); + } + /** * Get the request that this runtime node implements * @return the request diff --git a/src/jdk/nashorn/internal/ir/SplitNode.java b/src/jdk/nashorn/internal/ir/SplitNode.java index b751cdcb..49c4092f 100644 --- a/src/jdk/nashorn/internal/ir/SplitNode.java +++ b/src/jdk/nashorn/internal/ir/SplitNode.java @@ -25,99 +25,65 @@ package jdk.nashorn.internal.ir; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import jdk.nashorn.internal.codegen.CompileUnit; -import jdk.nashorn.internal.codegen.Label; -import jdk.nashorn.internal.codegen.MethodEmitter; -import jdk.nashorn.internal.ir.annotations.Ignore; -import jdk.nashorn.internal.ir.annotations.Reference; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; - /** * Node indicating code is split across classes. */ -public class SplitNode extends Node { +@Immutable +public class SplitNode extends LexicalContextNode { /** Split node method name. */ private final String name; /** Compilation unit. */ - private CompileUnit compileUnit; - - /** Method emitter for current method. */ - private MethodEmitter method; - - /** Method emitter for caller method. */ - private MethodEmitter caller; - - /** Containing function. */ - @Reference - private final FunctionNode functionNode; - - /** A list of target labels in parent methods this split node may encounter. */ - @Ignore - private final List<Label> externalTargets; - - /** True if this split node or any of its children contain a return statement. */ - private boolean hasReturn; + private final CompileUnit compileUnit; /** Body of split code. */ - @Ignore - private Node body; + private final Node body; /** * Constructor * - * @param name name of split node - * @param functionNode function node to split in - * @param body body of split code + * @param name name of split node + * @param body body of split code + * @param compileUnit compile unit to use for the body */ - public SplitNode(final String name, final FunctionNode functionNode, final Node body) { + public SplitNode(final String name, final Node body, final CompileUnit compileUnit) { super(body.getSource(), body.getToken(), body.getFinish()); - - this.name = name; - this.functionNode = functionNode; - this.body = body; - this.externalTargets = new ArrayList<>(); + this.name = name; + this.body = body; + this.compileUnit = compileUnit; } - private SplitNode(final SplitNode splitNode, final CopyState cs) { + private SplitNode(final SplitNode splitNode, final Node body) { super(splitNode); + this.name = splitNode.name; + this.body = body; + this.compileUnit = splitNode.compileUnit; + } - this.name = splitNode.name; - this.functionNode = (FunctionNode)cs.existingOrSame(splitNode.functionNode); - this.body = cs.existingOrCopy(splitNode.body); - this.externalTargets = new ArrayList<>(); + /** + * Get the body for this split node - i.e. the actual code it encloses + * @return body for split node + */ + public Node getBody() { + return body; } - @Override - protected Node copy(final CopyState cs) { - return new SplitNode(this, cs); + private SplitNode setBody(final LexicalContext lc, final Node body) { + if (this.body == body) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body)); } @Override - public Node accept(final NodeVisitor visitor) { - final CompileUnit saveCompileUnit = visitor.getCurrentCompileUnit(); - final MethodEmitter saveMethod = visitor.getCurrentMethodEmitter(); - - setCaller(saveMethod); - - visitor.setCurrentCompileUnit(getCompileUnit()); - visitor.setCurrentMethodEmitter(getMethodEmitter()); - - try { - if (visitor.enterSplitNode(this) != null) { - body = body.accept(visitor); - - return visitor.leaveSplitNode(this); - } - } finally { - visitor.setCurrentCompileUnit(saveCompileUnit); - visitor.setCurrentMethodEmitter(saveMethod); + public Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterSplitNode(this)) { + return visitor.leaveSplitNode(setBody(lc, body.accept(visitor))); } - return this; } @@ -130,22 +96,6 @@ public class SplitNode extends Node { } /** - * Get the method emitter of the caller for this split node - * @return caller method emitter - */ - public MethodEmitter getCaller() { - return caller; - } - - /** - * Set the caller method emitter for this split node - * @param caller method emitter - */ - public void setCaller(final MethodEmitter caller) { - this.caller = caller; - } - - /** * Get the name for this split node * @return name */ @@ -161,67 +111,4 @@ public class SplitNode extends Node { return compileUnit; } - /** - * Set the compile unit for this split node - * @param compileUnit compile unit - */ - public void setCompileUnit(final CompileUnit compileUnit) { - this.compileUnit = compileUnit; - } - - /** - * Get the method emitter for this split node - * @return method emitter - */ - public MethodEmitter getMethodEmitter() { - return method; - } - - /** - * Set the method emitter for this split node - * @param method method emitter - */ - public void setMethodEmitter(final MethodEmitter method) { - this.method = method; - } - - /** - * Get the function node this SplitNode splits - * @return function node reference - */ - public FunctionNode getFunctionNode() { - return functionNode; - } - - /** - * Get the external targets for this SplitNode - * @return list of external targets - */ - public List<Label> getExternalTargets() { - return Collections.unmodifiableList(externalTargets); - } - - /** - * Add an external target for this SplitNode - * @param targetLabel target label - */ - public void addExternalTarget(final Label targetLabel) { - externalTargets.add(targetLabel); - } - - /** - * Check whether this SplitNode returns a value - * @return true if return - */ - public boolean hasReturn() { - return hasReturn; - } - - /** - * Set whether this SplitNode returns a value or not - * @param hasReturn true if return exists, false otherwise - */ - public void setHasReturn(final boolean hasReturn) { - this.hasReturn = hasReturn; - } } diff --git a/src/jdk/nashorn/internal/ir/SwitchNode.java b/src/jdk/nashorn/internal/ir/SwitchNode.java index 23d9c7ea..7864a10d 100644 --- a/src/jdk/nashorn/internal/ir/SwitchNode.java +++ b/src/jdk/nashorn/internal/ir/SwitchNode.java @@ -28,73 +28,84 @@ package jdk.nashorn.internal.ir; import java.util.ArrayList; import java.util.Collections; import java.util.List; + import jdk.nashorn.internal.codegen.Label; -import jdk.nashorn.internal.ir.annotations.Ignore; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation of a SWITCH statement. */ -public class SwitchNode extends BreakableNode { +@Immutable +public final class SwitchNode extends BreakableNode { /** Switch expression. */ - private Node expression; - - /** Tag symbol. */ - private Symbol tag; + private final Node expression; /** Switch cases. */ - private List<CaseNode> cases; + private final List<CaseNode> cases; - /** Switch default. */ - @Ignore //points to one of the members in the list above, don't traverse twice - private CaseNode defaultCase; + /** Switch default index. */ + private final int defaultCaseIndex; + + /** Tag symbol. */ + private Symbol tag; /** * Constructor * - * @param source the source - * @param token token - * @param finish finish + * @param source the source + * @param token token + * @param finish finish + * @param expression switch expression + * @param cases cases + * @param defaultCase the default case node - null if none, otherwise has to be present in cases list */ - public SwitchNode(final Source source, final long token, final int finish) { - super(source, token, finish); - this.breakLabel = new Label("switch_break"); + public SwitchNode(final Source source, final long token, final int finish, final Node expression, final List<CaseNode> cases, final CaseNode defaultCase) { + super(source, token, finish, new Label("switch_break")); + this.expression = expression; + this.cases = cases; + this.defaultCaseIndex = defaultCase == null ? -1 : cases.indexOf(defaultCase); } - private SwitchNode(final SwitchNode switchNode, final CopyState cs) { + private SwitchNode(final SwitchNode switchNode, final Node expression, final List<CaseNode> cases, final int defaultCase) { super(switchNode); - - final List<CaseNode> newCases = new ArrayList<>(); - - for (final CaseNode caseNode : switchNode.getCases()) { - newCases.add((CaseNode)cs.existingOrCopy(caseNode)); - } - - this.expression = cs.existingOrCopy(switchNode.getExpression()); - this.tag = switchNode.getTag(); - this.cases = newCases; - this.defaultCase = (CaseNode)cs.existingOrCopy(switchNode.getDefaultCase()); - this.breakLabel = new Label(switchNode.getBreakLabel()); + this.expression = expression; + this.cases = cases; + this.defaultCaseIndex = defaultCase; + this.tag = switchNode.getTag(); //TODO are symbols inhereted as references? } @Override - protected Node copy(final CopyState cs) { - return new SwitchNode(this, cs); + public Node ensureUniqueLabels(final LexicalContext lc) { + final List<CaseNode> newCases = new ArrayList<>(); + for (final CaseNode caseNode : cases) { + newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody())); + } + return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex)); } @Override - public Node accept(final NodeVisitor visitor) { - if (visitor.enterSwitchNode(this) != null) { - expression = expression.accept(visitor); - - for (int i = 0, count = cases.size(); i < count; i++) { - cases.set(i, (CaseNode)cases.get(i).accept(visitor)); + public boolean isTerminal() { + //there must be a default case, and that including all other cases must terminate + if (!cases.isEmpty() && defaultCaseIndex != -1) { + for (final CaseNode caseNode : cases) { + if (!caseNode.isTerminal()) { + return false; + } } + return true; + } + return false; - //the default case is in the cases list and should not be explicitly traversed! + } - return visitor.leaveSwitchNode(this); + @Override + public Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterSwitchNode(this)) { + return visitor.leaveSwitchNode( + setExpression(visitor.getLexicalContext(), expression.accept(visitor)). + setCases(visitor.getLexicalContext(), Node.accept(visitor, CaseNode.class, cases), defaultCaseIndex)); } return this; @@ -108,6 +119,14 @@ public class SwitchNode extends BreakableNode { } /** + * Return the case node that is default case + * @return default case or null if none + */ + public CaseNode getDefaultCase() { + return defaultCaseIndex == -1 ? null : cases.get(defaultCaseIndex); + } + + /** * Get the cases in this switch * @return a list of case nodes */ @@ -116,27 +135,33 @@ public class SwitchNode extends BreakableNode { } /** - * Set or reset the list of cases in this switch - * @param cases a list of cases, case nodes + * Replace case nodes with new list. the cases have to be the same + * and the default case index the same. This is typically used + * by NodeVisitors who perform operations on every case node + * @param lc lexical context + * @param cases list of cases + * @return new switcy node or same if no state was changed */ - public void setCases(final List<CaseNode> cases) { - this.cases = cases; + public SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases) { + return setCases(lc, cases, defaultCaseIndex); } - /** - * Get the default case for this switch - * @return default case node - */ - public CaseNode getDefaultCase() { - return defaultCase; + private SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases, final int defaultCaseIndex) { + if (this.cases == cases) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex)); } /** - * Set the default case for this switch - * @param defaultCase default case node + * Set or reset the list of cases in this switch + * @param lc lexical context + * @param cases a list of cases, case nodes + * @param defaultCase a case in the list that is the default - must be in the list or class will assert + * @return new switch node or same if no state was changed */ - public void setDefaultCase(final CaseNode defaultCase) { - this.defaultCase = defaultCase; + public SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases, final CaseNode defaultCase) { + return setCases(lc, cases, defaultCase == null ? -1 : cases.indexOf(defaultCase)); } /** @@ -149,10 +174,15 @@ public class SwitchNode extends BreakableNode { /** * Set or reset the expression to switch on + * @param lc lexical context * @param expression switch expression + * @return new switch node or same if no state was changed */ - public void setExpression(final Node expression) { - this.expression = expression; + public SwitchNode setExpression(final LexicalContext lc, final Node expression) { + if (this.expression == expression) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex)); } /** diff --git a/src/jdk/nashorn/internal/ir/Symbol.java b/src/jdk/nashorn/internal/ir/Symbol.java index 603b8b08..da22f64d 100644 --- a/src/jdk/nashorn/internal/ir/Symbol.java +++ b/src/jdk/nashorn/internal/ir/Symbol.java @@ -29,8 +29,10 @@ import java.io.PrintWriter; import java.util.HashSet; import java.util.Set; import java.util.StringTokenizer; + import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.Debug; import jdk.nashorn.internal.runtime.options.Options; /** @@ -63,6 +65,8 @@ public final class Symbol implements Comparable<Symbol> { public static final int IS_LET = 1 << 8; /** Is this an internal symbol, never represented explicitly in source code */ public static final int IS_INTERNAL = 1 << 9; + /** Is this a function self-reference symbol */ + public static final int IS_FUNCTION_SELF = 1 << 10; /** Null or name identifying symbol. */ private final String name; @@ -70,12 +74,6 @@ public final class Symbol implements Comparable<Symbol> { /** Symbol flags. */ private int flags; - /** Defining node. */ - private Node node; - - /** Definition block. */ - private final Block block; - /** Type of symbol. */ private Type type; @@ -121,16 +119,12 @@ public final class Symbol implements Comparable<Symbol> { * * @param name name of symbol * @param flags symbol flags - * @param node node this symbol is in - * @param block block this symbol is in * @param type type of this symbol * @param slot bytecode slot for this symbol */ - protected Symbol(final String name, final int flags, final Node node, final Block block, final Type type, final int slot) { + protected Symbol(final String name, final int flags, final Type type, final int slot) { this.name = name; this.flags = flags; - this.node = node; - this.block = block; this.type = type; this.slot = slot; this.fieldIndex = -1; @@ -142,11 +136,9 @@ public final class Symbol implements Comparable<Symbol> { * * @param name name of symbol * @param flags symbol flags - * @param node node this symbol is in - * @param block block this symbol is in */ - public Symbol(final String name, final int flags, final Node node, final Block block) { - this(name, flags, node, block, Type.UNKNOWN, -1); + public Symbol(final String name, final int flags) { + this(name, flags, Type.UNKNOWN, -1); } /** @@ -157,7 +149,7 @@ public final class Symbol implements Comparable<Symbol> { * @param type type of this symbol */ public Symbol(final String name, final int flags, final Type type) { - this(name, flags, null, null, type, -1); + this(name, flags, type, -1); } private static String align(final String string, final int max) { @@ -269,20 +261,6 @@ public final class Symbol implements Comparable<Symbol> { return type.isCategory2() ? 2 : 1; } - @Override - public boolean equals(final Object other) { - if (!(other instanceof Symbol)) { - return false; - } - final Symbol symbol = (Symbol) other; - return name.equals(symbol.name) && block.equals(symbol.block); - } - - @Override - public int hashCode() { - return name.hashCode() ^ block.hashCode(); - } - private static String type(final String desc) { switch (desc.charAt(desc.length() - 1)) { case ';': @@ -371,14 +349,14 @@ public final class Symbol implements Comparable<Symbol> { /** * Flag this symbol as scope as described in {@link Symbol#isScope()} */ - public void setIsScope() { + /** + * Flag this symbol as scope as described in {@link Symbol#isScope()} + */ + public void setIsScope() { if (!isScope()) { trace("SET IS SCOPE"); } flags |= IS_SCOPE; - if(!isGlobal()) { - getBlock().setNeedsScope(); - } } /** @@ -478,11 +456,11 @@ public final class Symbol implements Comparable<Symbol> { } /** - * Get the block in which the symbol is defined - * @return a block + * Flag this symbol as a function's self-referencing symbol. + * @return true if this symbol as a function's self-referencing symbol. */ - public Block getBlock() { - return block; + public boolean isFunctionSelf() { + return (flags & IS_FUNCTION_SELF) == IS_FUNCTION_SELF; } /** @@ -492,7 +470,7 @@ public final class Symbol implements Comparable<Symbol> { * @return field index */ public int getFieldIndex() { - assert fieldIndex != -1 : "fieldIndex must be initialized"; + assert fieldIndex != -1 : "fieldIndex must be initialized " + fieldIndex; return fieldIndex; } @@ -503,7 +481,6 @@ public final class Symbol implements Comparable<Symbol> { * @param fieldIndex field index - a positive integer */ public void setFieldIndex(final int fieldIndex) { - assert this.fieldIndex == -1 : "fieldIndex must be initialized only once"; this.fieldIndex = fieldIndex; } @@ -524,22 +501,6 @@ public final class Symbol implements Comparable<Symbol> { } /** - * Get the node this symbol stores the result for - * @return node - */ - public Node getNode() { - return node; - } - - /** - * Set the node this symbol stores the result for - * @param node node - */ - public void setNode(final Node node) { - this.node = node; - } - - /** * Get the name of this symbol * @return symbol name */ @@ -616,18 +577,25 @@ public final class Symbol implements Comparable<Symbol> { } /** - * Check if this symbol is in the global scope, i.e. it is on the outermost level - * in the script - * @return true if this this is a global scope symbol + * From a lexical context, set this symbol as needing scope, which + * will set flags for the defining block that will be written when + * block is popped from the lexical context stack, used by codegen + * when flags need to be tagged, but block is in the + * middle of evaluation and cannot be modified. + * + * @param lc lexical context + * @param symbol symbol */ - public boolean isTopLevel() { - return block instanceof FunctionNode && ((FunctionNode) block).isProgram(); + public static void setSymbolIsScope(final LexicalContext lc, final Symbol symbol) { + symbol.setIsScope(); + if (!symbol.isGlobal()) { + lc.setFlag(lc.getDefiningBlock(symbol), Block.NEEDS_SCOPE); + } } - private void trace(final String desc) { if (TRACE_SYMBOLS != null && (TRACE_SYMBOLS.isEmpty() || TRACE_SYMBOLS.contains(name))) { - Context.err("SYMBOL: '" + name + "' " + desc); + Context.err(Debug.id(this) + " SYMBOL: '" + name + "' " + desc); if (TRACE_SYMBOLS_STACKTRACE != null && (TRACE_SYMBOLS_STACKTRACE.isEmpty() || TRACE_SYMBOLS_STACKTRACE.contains(name))) { new Throwable().printStackTrace(Context.getCurrentErr()); } diff --git a/src/jdk/nashorn/internal/ir/TernaryNode.java b/src/jdk/nashorn/internal/ir/TernaryNode.java index de333851..e2ccdb91 100644 --- a/src/jdk/nashorn/internal/ir/TernaryNode.java +++ b/src/jdk/nashorn/internal/ir/TernaryNode.java @@ -25,15 +25,21 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * TernaryNode nodes represent three operand operations (?:). */ -public class TernaryNode extends BinaryNode { +@Immutable +public final class TernaryNode extends Node { + private final Node lhs; + + private final Node rhs; + /** Third argument. */ - private Node third; + private final Node third; /** * Constructor @@ -45,43 +51,26 @@ public class TernaryNode extends BinaryNode { * @param third third node */ public TernaryNode(final Source source, final long token, final Node lhs, final Node rhs, final Node third) { - super(source, token, lhs, rhs); - - this.finish = third.getFinish(); + super(source, token, third.getFinish()); + this.lhs = lhs; + this.rhs = rhs; this.third = third; } - private TernaryNode(final TernaryNode ternaryNode, final CopyState cs) { - super(ternaryNode, cs); - - this.third = cs.existingOrCopy(ternaryNode.third); - } - - @Override - protected Node copy(final CopyState cs) { - return new TernaryNode(this, cs); - } - - @Override - public boolean equals(final Object other) { - if (!super.equals(other)) { - return false; - } - return third.equals(((TernaryNode)other).third()); - } - - @Override - public int hashCode() { - return super.hashCode() ^ third().hashCode(); + private TernaryNode(final TernaryNode ternaryNode, final Node lhs, final Node rhs, final Node third) { + super(ternaryNode); + this.lhs = lhs; + this.rhs = rhs; + this.third = third; } @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterTernaryNode(this) != null) { + if (visitor.enterTernaryNode(this)) { final Node newLhs = lhs().accept(visitor); final Node newRhs = rhs().accept(visitor); final Node newThird = third.accept(visitor); - return visitor.leaveTernaryNode((TernaryNode)setThird(newThird).setLHS(newLhs).setRHS(newRhs)); + return visitor.leaveTernaryNode(setThird(newThird).setLHS(newLhs).setRHS(newRhs)); } return this; @@ -123,6 +112,22 @@ public class TernaryNode extends BinaryNode { } /** + * Get the lhs node for this ternary expression, i.e. "x" in x ? y : z + * @return a node + */ + public Node lhs() { + return lhs; + } + + /** + * Get the rhs node for this ternary expression, i.e. "y" in x ? y : z + * @return a node + */ + public Node rhs() { + return rhs; + } + + /** * Get the "third" node for this ternary expression, i.e. "z" in x ? y : z * @return a node */ @@ -131,14 +136,38 @@ public class TernaryNode extends BinaryNode { } /** + * Set the left hand side expression for this node + * @param lhs new left hand side expression + * @return a node equivalent to this one except for the requested change. + */ + public TernaryNode setLHS(final Node lhs) { + if (this.lhs == lhs) { + return this; + } + return new TernaryNode(this, lhs, rhs, third); + } + + /** + * Set the right hand side expression for this node + * @param rhs new left hand side expression + * @return a node equivalent to this one except for the requested change. + */ + public TernaryNode setRHS(final Node rhs) { + if (this.rhs == rhs) { + return this; + } + return new TernaryNode(this, lhs, rhs, third); + } + + /** * Reset the "third" node for this ternary expression, i.e. "z" in x ? y : z * @param third a node * @return a node equivalent to this one except for the requested change. */ public TernaryNode setThird(final Node third) { - if(this.third == third) return this; - final TernaryNode n = (TernaryNode)clone(); - n.third = third; - return n; + if (this.third == third) { + return this; + } + return new TernaryNode(this, lhs, rhs, third); } } diff --git a/src/jdk/nashorn/internal/ir/ThrowNode.java b/src/jdk/nashorn/internal/ir/ThrowNode.java index ab6d59e2..7a10a6ad 100644 --- a/src/jdk/nashorn/internal/ir/ThrowNode.java +++ b/src/jdk/nashorn/internal/ir/ThrowNode.java @@ -25,20 +25,17 @@ package jdk.nashorn.internal.ir; -import jdk.nashorn.internal.ir.annotations.Ignore; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for THROW statements. */ -public class ThrowNode extends Node { +@Immutable +public final class ThrowNode extends Node { /** Exception expression. */ - private Node expression; - - /** Try chain. */ - @Ignore - private final TryNode tryChain; + private final Node expression; /** * Constructor @@ -47,26 +44,21 @@ public class ThrowNode extends Node { * @param token token * @param finish finish * @param expression expression to throw - * @param tryChain surrounding try chain */ - public ThrowNode(final Source source, final long token, final int finish, final Node expression, final TryNode tryChain) { + public ThrowNode(final Source source, final long token, final int finish, final Node expression) { super(source, token, finish); this.expression = expression; - this.tryChain = tryChain; - setIsTerminal(true); } - private ThrowNode(final ThrowNode throwNode, final CopyState cs) { - super(throwNode); - - this.expression = cs.existingOrCopy(throwNode.expression); - this.tryChain = (TryNode)cs.existingOrSame(throwNode.tryChain); + private ThrowNode(final Node node, final Node expression) { + super(node); + this.expression = expression; } @Override - protected Node copy(final CopyState cs) { - return new ThrowNode(this, cs); + public boolean isTerminal() { + return true; } /** @@ -75,9 +67,8 @@ public class ThrowNode extends Node { */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterThrowNode(this) != null) { - setExpression(expression.accept(visitor)); - return visitor.leaveThrowNode(this); + if (visitor.enterThrowNode(this)) { + return visitor.leaveThrowNode(setExpression(expression.accept(visitor))); } return this; @@ -103,16 +94,13 @@ public class ThrowNode extends Node { /** * Reset the expression being thrown by this node * @param expression new expression + * @return new or same thrownode */ - public void setExpression(final Node expression) { - this.expression = expression; + public ThrowNode setExpression(final Node expression) { + if (this.expression == expression) { + return this; + } + return new ThrowNode(this, expression); } - /** - * Get surrounding tryChain for this node - * @return try chain - */ - public TryNode getTryChain() { - return tryChain; - } } diff --git a/src/jdk/nashorn/internal/ir/TryNode.java b/src/jdk/nashorn/internal/ir/TryNode.java index 7d3864bc..5e3ff7af 100644 --- a/src/jdk/nashorn/internal/ir/TryNode.java +++ b/src/jdk/nashorn/internal/ir/TryNode.java @@ -28,30 +28,28 @@ package jdk.nashorn.internal.ir; import java.util.ArrayList; import java.util.Collections; import java.util.List; + import jdk.nashorn.internal.codegen.Label; -import jdk.nashorn.internal.ir.annotations.Ignore; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation of a TRY statement. */ -public class TryNode extends Node { - /** Try chain. */ - @Ignore //don't print, will be apparent from the AST - private TryNode next; - +@Immutable +public final class TryNode extends Node { /** Try statements. */ - private Block body; + private final Block body; /** List of catch clauses. */ - private List<Block> catchBlocks; + private final List<Block> catchBlocks; /** Finally clause. */ - private Block finallyBody; + private final Block finallyBody; /** Exit label. */ - private Label exit; + private final Label exit; /** Exception symbol. */ private Symbol exception; @@ -62,37 +60,46 @@ public class TryNode extends Node { /** * Constructor * - * @param source the source - * @param token token - * @param finish finish - * @param next next try node in chain - */ - public TryNode(final Source source, final long token, final int finish, final TryNode next) { + * @param source the source + * @param token token + * @param finish finish + * @param body try node body + * @param catchBlocks list of catch blocks in order + * @param finallyBody body of finally block or null if none + */ + public TryNode(final Source source, final long token, final int finish, final Block body, final List<Block> catchBlocks, final Block finallyBody) { super(source, token, finish); - - this.next = next; + this.body = body; + this.catchBlocks = catchBlocks; + this.finallyBody = finallyBody; this.exit = new Label("exit"); } - private TryNode(final TryNode tryNode, final CopyState cs) { + private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody) { super(tryNode); + this.body = body; + this.catchBlocks = catchBlocks; + this.finallyBody = finallyBody; + this.exit = new Label(tryNode.exit); + } - final List<Block> newCatchBlocks = new ArrayList<>(); - - for (final Block block : tryNode.catchBlocks) { - newCatchBlocks.add((Block)cs.existingOrCopy(block)); - } - - this.next = (TryNode)cs.existingOrSame(tryNode.getNext()); - this.body = (Block)cs.existingOrCopy(tryNode.getBody()); - this.catchBlocks = newCatchBlocks; - this.finallyBody = (Block)cs.existingOrCopy(tryNode.getFinallyBody()); - this.exit = new Label(tryNode.getExit()); + @Override + public Node ensureUniqueLabels(final LexicalContext lc) { + //try nodes are never in lex context + return new TryNode(this, body, catchBlocks, finallyBody); } @Override - protected Node copy(final CopyState cs) { - return new TryNode(this, cs); + public boolean isTerminal() { + if (body.isTerminal()) { + for (final Block catchBlock : getCatchBlocks()) { + if (!catchBlock.isTerminal()) { + return false; + } + } + return true; + } + return false; } /** @@ -101,21 +108,16 @@ public class TryNode extends Node { */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterTryNode(this) != null) { - // Need to do first for termination analysis. - if (finallyBody != null) { - finallyBody = (Block)finallyBody.accept(visitor); - } - - body = (Block)body.accept(visitor); - - final List<Block> newCatchBlocks = new ArrayList<>(catchBlocks.size()); - for (final Block catchBlock : catchBlocks) { - newCatchBlocks.add((Block)catchBlock.accept(visitor)); - } - this.catchBlocks = newCatchBlocks; - - return visitor.leaveTryNode(this); + if (visitor.enterTryNode(this)) { + // Need to do finallybody first for termination analysis. TODO still necessary? + final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor); + final Block newBody = (Block)body.accept(visitor); + return visitor.leaveTryNode( + setBody(newBody). + setFinallyBody(newFinallyBody). + setCatchBlocks(Node.accept(visitor, Block.class, catchBlocks)). + setException(exception). + setFinallyCatchAll(finallyCatchAll)); } return this; @@ -123,7 +125,7 @@ public class TryNode extends Node { @Override public void toString(final StringBuilder sb) { - sb.append("try"); + sb.append("try "); } /** @@ -137,9 +139,13 @@ public class TryNode extends Node { /** * Reset the body of this try block * @param body new body + * @return new TryNode or same if unchanged */ - public void setBody(final Block body) { - this.body = body; + public TryNode setBody(final Block body) { + if (this.body == body) { + return this; + } + return new TryNode(this, body, catchBlocks, finallyBody); } /** @@ -151,16 +157,7 @@ public class TryNode extends Node { for (final Block catchBlock : catchBlocks) { catches.add((CatchNode)catchBlock.getStatements().get(0)); } - return catches; - } - - /** - * Returns true if the specified block is the body of this try block, or any of its catch blocks. - * @param block the block - * @return true if the specified block is the body of this try block, or any of its catch blocks. - */ - public boolean isChildBlock(Block block) { - return body == block || catchBlocks.contains(block); + return Collections.unmodifiableList(catches); } /** @@ -174,9 +171,13 @@ public class TryNode extends Node { /** * Set the catch blocks of this try * @param catchBlocks list of catch blocks + * @return new TryNode or same if unchanged */ - public void setCatchBlocks(final List<Block> catchBlocks) { - this.catchBlocks = catchBlocks; + public TryNode setCatchBlocks(final List<Block> catchBlocks) { + if (this.catchBlocks == catchBlocks) { + return this; + } + return new TryNode(this, body, catchBlocks, finallyBody); } /** @@ -190,9 +191,11 @@ public class TryNode extends Node { /** * Set the exception symbol for this try block * @param exception a symbol for the compiler to store the exception in + * @return new TryNode or same if unchanged */ - public void setException(final Symbol exception) { + public TryNode setException(final Symbol exception) { this.exception = exception; + return this; } /** @@ -207,9 +210,13 @@ public class TryNode extends Node { * If a finally block exists, the synthetic catchall needs another symbol to * store its throwable * @param finallyCatchAll a symbol for the finally catch all exception + * @return new TryNode or same if unchanged + * + * TODO can this still be stateful? */ - public void setFinallyCatchAll(final Symbol finallyCatchAll) { + public TryNode setFinallyCatchAll(final Symbol finallyCatchAll) { this.finallyCatchAll = finallyCatchAll; + return this; } /** @@ -221,14 +228,6 @@ public class TryNode extends Node { } /** - * Set the exit label for this try block - * @param exit label - */ - public void setExit(final Label exit) { - this.exit = exit; - } - - /** * Get the body of the finally clause for this try * @return finally body, or null if no finally */ @@ -239,24 +238,12 @@ public class TryNode extends Node { /** * Set the finally body of this try * @param finallyBody new finally body + * @return new TryNode or same if unchanged */ - public void setFinallyBody(final Block finallyBody) { - this.finallyBody = finallyBody; - } - - /** - * Get next try node in try chain - * @return next try node - */ - public TryNode getNext() { - return next; - } - - /** - * Set next try node in try chain - * @param next next try node - */ - public void setNext(final TryNode next) { - this.next = next; + public TryNode setFinallyBody(final Block finallyBody) { + if (this.finallyBody == finallyBody) { + return this; + } + return new TryNode(this, body, catchBlocks, finallyBody); } } diff --git a/src/jdk/nashorn/internal/ir/UnaryNode.java b/src/jdk/nashorn/internal/ir/UnaryNode.java index d823c058..fed5e408 100644 --- a/src/jdk/nashorn/internal/ir/UnaryNode.java +++ b/src/jdk/nashorn/internal/ir/UnaryNode.java @@ -31,6 +31,7 @@ import static jdk.nashorn.internal.parser.TokenType.DECPOSTFIX; import static jdk.nashorn.internal.parser.TokenType.INCPOSTFIX; 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; @@ -39,9 +40,10 @@ import jdk.nashorn.internal.runtime.Source; /** * UnaryNode nodes represent single operand operations. */ -public class UnaryNode extends Node implements Assignment<Node> { +@Immutable +public final class UnaryNode extends Node implements Assignment<Node> { /** Right hand side argument. */ - private Node rhs; + private final Node rhs; /** * Constructor @@ -51,23 +53,26 @@ public class UnaryNode extends Node implements Assignment<Node> { * @param rhs expression */ public UnaryNode(final Source source, final long token, final Node rhs) { - super(source, token, Token.descPosition(token)); - - this.start = Math.min(rhs.getStart(), Token.descPosition(token)); - this.finish = Math.max(Token.descPosition(token) + Token.descLength(token), rhs.getFinish()); - this.rhs = rhs; + this(source, token, Math.min(rhs.getStart(), Token.descPosition(token)), Math.max(Token.descPosition(token) + Token.descLength(token), rhs.getFinish()), rhs); } /** - * Copy constructor - * - * @param unaryNode source node - * @param cs copy state + * Constructor + * @param source the source + * @param token token + * @param start start + * @param finish finish + * @param rhs expression */ - protected UnaryNode(final UnaryNode unaryNode, final CopyState cs) { - super(unaryNode); + public UnaryNode(final Source source, final long token, final int start, final int finish, final Node rhs) { + super(source, token, start, finish); + this.rhs = rhs; + } + - this.rhs = cs.existingOrCopy(unaryNode.rhs); + private UnaryNode(final UnaryNode unaryNode, final Node rhs) { + super(unaryNode); + this.rhs = rhs; } /** @@ -113,31 +118,13 @@ public class UnaryNode extends Node implements Assignment<Node> { return getAssignmentDest(); } - @Override - public boolean equals(final Object other) { - if (!super.equals(other)) { - return false; - } - return rhs.equals(((UnaryNode)other).rhs()); - } - - @Override - public int hashCode() { - return super.hashCode() ^ rhs().hashCode(); - } - - @Override - protected Node copy(final CopyState cs) { - return new UnaryNode(this, cs); - } - /** * Assist in IR navigation. * @param visitor IR navigating visitor. */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterUnaryNode(this) != null) { + if (visitor.enterUnaryNode(this)) { return visitor.leaveUnaryNode(setRHS(rhs.accept(visitor))); } @@ -219,9 +206,9 @@ public class UnaryNode extends Node implements Assignment<Node> { * @return a node equivalent to this one except for the requested change. */ public UnaryNode setRHS(final Node rhs) { - if(this.rhs == rhs) return this; - final UnaryNode n = (UnaryNode)clone(); - n.rhs = rhs; - return n; + if (this.rhs == rhs) { + return this; + } + return new UnaryNode(this, rhs); } } diff --git a/src/jdk/nashorn/internal/ir/VarNode.java b/src/jdk/nashorn/internal/ir/VarNode.java index b719c99d..fbc3eabe 100644 --- a/src/jdk/nashorn/internal/ir/VarNode.java +++ b/src/jdk/nashorn/internal/ir/VarNode.java @@ -25,21 +25,31 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * Node represents a var/let declaration. */ -public class VarNode extends Node implements Assignment<IdentNode> { +@Immutable +public final class VarNode extends Node implements Assignment<IdentNode> { /** Var name. */ - private IdentNode name; + private final IdentNode name; /** Initialization expression. */ - private Node init; + private final Node init; /** Is this a var statement (as opposed to a "var" in a for loop statement) */ - private final boolean isStatement; + private final int flags; + + /** Flag that determines if this function node is a statement */ + public static final int IS_STATEMENT = 1 << 0; + + /** Flag that determines if this is the last function declaration in a function + * This is used to micro optimize the placement of return value assignments for + * a program node */ + public static final int IS_LAST_FUNCTION_DECLARATION = 1 << 1; /** * Constructor @@ -51,7 +61,14 @@ public class VarNode extends Node implements Assignment<IdentNode> { * @param init init node or null if just a declaration */ public VarNode(final Source source, final long token, final int finish, final IdentNode name, final Node init) { - this(source, token, finish, name, init, true); + this(source, token, finish, name, init, IS_STATEMENT); + } + + private VarNode(final VarNode varNode, final IdentNode name, final Node init, final int flags) { + super(varNode); + this.name = init == null ? name : name.setIsInitializedHere(); + this.init = init; + this.flags = flags; } /** @@ -62,28 +79,14 @@ public class VarNode extends Node implements Assignment<IdentNode> { * @param finish finish * @param name name of variable * @param init init node or null if just a declaration - * @param isStatement if this is a var statement (true), or a for-loop initializer (false) + * @param flags flags */ - public VarNode(final Source source, final long token, final int finish, final IdentNode name, final Node init, boolean isStatement) { + public VarNode(final Source source, final long token, final int finish, final IdentNode name, final Node init, final int flags) { super(source, token, finish); this.name = init == null ? name : name.setIsInitializedHere(); this.init = init; - this.isStatement = isStatement; - } - - - private VarNode(final VarNode varNode, final CopyState cs) { - super(varNode); - - this.name = (IdentNode)cs.existingOrCopy(varNode.name); - this.init = cs.existingOrCopy(varNode.init); - this.isStatement = varNode.isStatement; - } - - @Override - protected Node copy(final CopyState cs) { - return new VarNode(this, cs); + this.flags = flags; } @Override @@ -115,45 +118,17 @@ public class VarNode extends Node implements Assignment<IdentNode> { } /** - * Test to see if two VarNodes are the same. - * @param other Other VarNode. - * @return True if the VarNodes are the same. - */ - @Override - public boolean equals(final Object other) { - if (other instanceof VarNode) { - final VarNode otherNode = (VarNode)other; - final boolean nameMatches = name.equals(otherNode.name); - if (hasInit() != otherNode.hasInit()) { - return false; - } else if (init == null) { - return nameMatches; - } else { - return nameMatches && init.equals(otherNode.init); - } - } - return false; - } - - @Override - public int hashCode() { - return name.hashCode() ^ (init == null ? 0 : init.hashCode()); - } - - /** * Assist in IR navigation. * @param visitor IR navigating visitor. */ @Override public Node accept(final NodeVisitor visitor) { - if (visitor.enterVarNode(this) != null) { + if (visitor.enterVarNode(this)) { final IdentNode newName = (IdentNode)name.accept(visitor); - final Node newInit = init == null ? null : init.accept(visitor); - final VarNode newThis; - if(name != newName || init != newInit) { - newThis = (VarNode)clone(); - newThis.init = newInit; - newThis.name = newInit == null ? newName : newName.setIsInitializedHere(); + final Node newInit = init == null ? null : init.accept(visitor); + final VarNode newThis; + if (name != newName || init != newInit) { + newThis = new VarNode(this, newName, newInit, flags); } else { newThis = this; } @@ -187,10 +162,10 @@ public class VarNode extends Node implements Assignment<IdentNode> { * @return a node equivalent to this one except for the requested change. */ public VarNode setInit(final Node init) { - if(this.init == init) return this; - final VarNode n = (VarNode)clone(); - n.init = init; - return n; + if (this.init == init) { + return this; + } + return new VarNode(this, name, init, flags); } /** @@ -204,12 +179,38 @@ public class VarNode extends Node implements Assignment<IdentNode> { /** * Reset the identifier for this VarNode * @param name new IdentNode representing the variable being set or declared + * @return a node equivalent to this one except for the requested change. + */ + public VarNode setName(final IdentNode name) { + if (this.name == name) { + return this; + } + return new VarNode(this, name, init, flags); + } + + private VarNode setFlags(final int flags) { + if (this.flags == flags) { + return this; + } + return new VarNode(this, name, init, flags); + } + + /** + * Check if a flag is set for this var node + * @param flag flag + * @return true if flag is set + */ + public boolean getFlag(final int flag) { + return (flags & flag) == flag; + } + + /** + * Set a flag for this var node + * @param flag flag + * @return new node if flags changed, same otherwise */ - private VarNode setName(final IdentNode name) { - if(this.name == name) return this; - final VarNode n = (VarNode)clone(); - n.name = name; - return n; + public VarNode setFlag(final int flag) { + return setFlags(flags | flag); } /** @@ -217,7 +218,7 @@ public class VarNode extends Node implements Assignment<IdentNode> { * @return true if this is a var statement (as opposed to a var initializer in a for loop). */ public boolean isStatement() { - return isStatement; + return (flags & IS_STATEMENT) != 0; } /** diff --git a/src/jdk/nashorn/internal/ir/WhileNode.java b/src/jdk/nashorn/internal/ir/WhileNode.java index 8db31c08..438be013 100644 --- a/src/jdk/nashorn/internal/ir/WhileNode.java +++ b/src/jdk/nashorn/internal/ir/WhileNode.java @@ -25,7 +25,7 @@ package jdk.nashorn.internal.ir; -import jdk.nashorn.internal.codegen.Label; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; @@ -33,130 +33,126 @@ import jdk.nashorn.internal.runtime.Source; * IR representation for a WHILE statement. This is the superclass of all * loop nodes */ -public class WhileNode extends BreakableNode { - /** Test expression. */ - protected Node test; +@Immutable +public final class WhileNode extends LoopNode { - /** For body. */ - protected Block body; - - /** loop continue label. */ - protected Label continueLabel; + /** is this a do while node ? */ + private final boolean isDoWhile; /** * Constructor * - * @param source the source - * @param token token - * @param finish finish + * @param source the source + * @param token token + * @param finish finish + * @param isDoWhile is this a do while loop? */ - public WhileNode(final Source source, final long token, final int finish) { - super(source, token, finish); - - this.breakLabel = new Label("while_break"); - this.continueLabel = new Label("while_continue"); + public WhileNode(final Source source, final long token, final int finish, final boolean isDoWhile) { + super(source, token, finish, null, null, false); + this.isDoWhile = isDoWhile; } /** - * Copy constructor + * Internal copy constructor * - * @param whileNode source node - * @param cs copy state + * @param whileNode while node + * @param test test + * @param body body + * @param controlFlowEscapes control flow escapes? */ - protected WhileNode(final WhileNode whileNode, final CopyState cs) { - super(whileNode); - - this.test = cs.existingOrCopy(whileNode.test); - this.body = (Block)cs.existingOrCopy(whileNode.body); - this.breakLabel = new Label(whileNode.breakLabel); - this.continueLabel = new Label(whileNode.continueLabel); + protected WhileNode(final WhileNode whileNode, final Node test, final Block body, final boolean controlFlowEscapes) { + super(whileNode, test, body, controlFlowEscapes); + this.isDoWhile = whileNode.isDoWhile; } @Override - protected Node copy(final CopyState cs) { - return new WhileNode(this, cs); + public Node ensureUniqueLabels(final LexicalContext lc) { + return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes)); } @Override - public boolean isLoop() { - return true; + public boolean hasGoto() { + return test == null; } - /** - * Assist in IR navigation. - * @param visitor IR navigating visitor. - */ @Override - public Node accept(final NodeVisitor visitor) { - if (visitor.enterWhileNode(this) != null) { - test = test.accept(visitor); - body = (Block)body.accept(visitor); + protected Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterWhileNode(this)) { + if (isDoWhile()) { + return visitor.leaveWhileNode( + setTest(lc, test.accept(visitor)). + setBody(lc, (Block)body.accept(visitor))); + } + return visitor.leaveWhileNode( + setBody(lc, (Block)body.accept(visitor)). + setTest(lc, test.accept(visitor))); - return visitor.leaveWhileNode(this); } return this; } @Override - public void toString(final StringBuilder sb) { - sb.append("while ("); - test.toString(sb); - sb.append(')'); + public Node getTest() { + return test; } - /** - * Get the loop body - * @return body - */ - public Block getBody() { - return body; + @Override + public WhileNode setTest(final LexicalContext lc, final Node test) { + if (this.test == test) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes)); } - /** - * Reset the loop body - * @param body new body - */ - public void setBody(final Block body) { - this.body = body; + @Override + public Block getBody() { + return body; } - /** - * Set the break label (described in {@link WhileNode#getBreakLabel()} for this while node - * @param breakLabel break label - */ - public void setBreakLabel(final Label breakLabel) { - this.breakLabel = breakLabel; + @Override + public WhileNode setBody(final LexicalContext lc, final Block body) { + if (this.body == body) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes)); } - /** - * Get the continue label for this while node, i.e. location to go to on continue - * @return continue label - */ - public Label getContinueLabel() { - return continueLabel; + @Override + public WhileNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes) { + if (this.controlFlowEscapes == controlFlowEscapes) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes)); } /** - * Set the continue label (described in {@link WhileNode#getContinueLabel()} for this while node - * @param continueLabel continue label + * Check if this is a do while loop or a normal while loop + * @return true if do while */ - public void setContinueLabel(final Label continueLabel) { - this.continueLabel = continueLabel; + public boolean isDoWhile() { + return isDoWhile; } - /** - * Get the test expression for this loop, that upon evaluation to true does another iteration - * @return test expression - */ - public Node getTest() { - return test; + @Override + public void toString(final StringBuilder sb) { + if (isDoWhile()) { + sb.append("do {"); + body.toString(sb); + sb.append("} while ("); + test.toString(sb); + sb.append(')'); + } else { + sb.append("while ("); + test.toString(sb); + sb.append(')'); + } } - /** - * Set the test expression for this loop - * @param test test expression, null if infinite loop - */ - public void setTest(final Node test) { - this.test = test; + @Override + public boolean mustEnter() { + if (isDoWhile()) { + return true; + } + return test == null; } } diff --git a/src/jdk/nashorn/internal/ir/WithNode.java b/src/jdk/nashorn/internal/ir/WithNode.java index f5ad3b13..5ebbfd55 100644 --- a/src/jdk/nashorn/internal/ir/WithNode.java +++ b/src/jdk/nashorn/internal/ir/WithNode.java @@ -25,18 +25,20 @@ package jdk.nashorn.internal.ir; +import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.Source; /** * IR representation for {@code with} statements. */ -public class WithNode extends Node { +@Immutable +public final class WithNode extends LexicalContextNode { /** This expression. */ - private Node expression; + private final Node expression; /** Statements. */ - private Block body; + private final Block body; /** * Constructor @@ -44,26 +46,19 @@ public class WithNode extends Node { * @param source the source * @param token token * @param finish finish - * @param expression expression in parenthesis - * @param body with node body */ - public WithNode(final Source source, final long token, final int finish, final Node expression, final Block body) { + public WithNode(final Source source, final long token, final int finish) { super(source, token, finish); - this.expression = expression; - this.body = body; + this.expression = null; + this.body = null; } - private WithNode(final WithNode withNode, final CopyState cs) { - super(withNode); - - this.expression = cs.existingOrCopy(withNode.expression); - this.body = (Block)cs.existingOrCopy(withNode.body); - } + private WithNode(final WithNode node, final Node expression, final Block body) { + super(node); - @Override - protected Node copy(final CopyState cs) { - return new WithNode(this, cs); + this.expression = expression; + this.body = body; } /** @@ -72,17 +67,21 @@ public class WithNode extends Node { * @param visitor IR navigating visitor. */ @Override - public Node accept(final NodeVisitor visitor) { - if (visitor.enterWithNode(this) != null) { - expression = expression.accept(visitor); - body = (Block)body.accept(visitor); - return visitor.leaveWithNode(this); + public Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterWithNode(this)) { + return visitor.leaveWithNode( + setExpression(lc, expression.accept(visitor)). + setBody(lc, (Block)body.accept(visitor))); } - return this; } @Override + public boolean isTerminal() { + return body.isTerminal(); + } + + @Override public void toString(final StringBuilder sb) { sb.append("with ("); expression.toString(sb); @@ -99,10 +98,15 @@ public class WithNode extends Node { /** * Reset the body of this with node + * @param lc lexical context * @param body new body + * @return new or same withnode */ - public void setBody(final Block body) { - this.body = body; + public WithNode setBody(final LexicalContext lc, final Block body) { + if (this.body == body) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new WithNode(this, expression, body)); } /** @@ -115,10 +119,15 @@ public class WithNode extends Node { /** * Reset the expression of this with node + * @param lc lexical context * @param expression new expression + * @return new or same withnode */ - public void setExpression(final Node expression) { - this.expression = expression; + public WithNode setExpression(final LexicalContext lc, final Node expression) { + if (this.expression == expression) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new WithNode(this, expression, body)); } } diff --git a/src/jdk/nashorn/internal/ir/annotations/Immutable.java b/src/jdk/nashorn/internal/ir/annotations/Immutable.java new file mode 100644 index 00000000..663abf7c --- /dev/null +++ b/src/jdk/nashorn/internal/ir/annotations/Immutable.java @@ -0,0 +1,34 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.ir.annotations; + +/** + * Tag for nodes that are immutable. To be immutable all fields must be + * final and copy on write semantics must be in place + */ +public @interface Immutable { + //empty +} diff --git a/src/jdk/nashorn/internal/ir/debug/ASTWriter.java b/src/jdk/nashorn/internal/ir/debug/ASTWriter.java index 7bbe3836..5d9b5dfb 100644 --- a/src/jdk/nashorn/internal/ir/debug/ASTWriter.java +++ b/src/jdk/nashorn/internal/ir/debug/ASTWriter.java @@ -33,7 +33,9 @@ import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; import java.util.List; + import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.annotations.Ignore; @@ -113,6 +115,10 @@ public final class ASTWriter { type += "#" + node.getSymbol(); } + if (node instanceof Block && ((Block)node).needsScope()) { + type += " <scope>"; + } + final List<Field> children = new LinkedList<>(); if (!isReference) { @@ -121,10 +127,6 @@ public final class ASTWriter { String status = ""; - if (node.shouldDiscard()) { - status += " Discard"; - } - if (node.isTerminal()) { status += " Terminal"; } diff --git a/src/jdk/nashorn/internal/ir/debug/JSONWriter.java b/src/jdk/nashorn/internal/ir/debug/JSONWriter.java index a8c3c4a4..988b756c 100644 --- a/src/jdk/nashorn/internal/ir/debug/JSONWriter.java +++ b/src/jdk/nashorn/internal/ir/debug/JSONWriter.java @@ -36,7 +36,6 @@ import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; -import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.EmptyNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; @@ -88,7 +87,7 @@ public final class JSONWriter extends NodeVisitor { final Parser parser = new Parser(env, new Source(name, code), new Context.ThrowErrorManager(), env._strict); final JSONWriter jsonWriter = new JSONWriter(includeLoc); try { - final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.tag()); + final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.symbolName()); functionNode.accept(jsonWriter); return jsonWriter.getString(); } catch (final ParserException e) { @@ -98,11 +97,16 @@ public final class JSONWriter extends NodeVisitor { } @Override - protected Node enterDefault(final Node node) { + protected boolean enterDefault(final Node node) { objectStart(); location(node); - return node; + return true; + } + + private boolean leave() { + objectEnd(); + return false; } @Override @@ -112,7 +116,7 @@ public final class JSONWriter extends NodeVisitor { } @Override - public Node enterAccessNode(final AccessNode accessNode) { + public boolean enterAccessNode(final AccessNode accessNode) { enterDefault(accessNode); type("MemberExpression"); @@ -128,11 +132,11 @@ public final class JSONWriter extends NodeVisitor { property("computed", false); - return leaveDefault(accessNode); + return leave(); } @Override - public Node enterBlock(final Block block) { + public boolean enterBlock(final Block block) { enterDefault(block); type("BlockStatement"); @@ -140,21 +144,21 @@ public final class JSONWriter extends NodeVisitor { array("body", block.getStatements()); - return leaveDefault(block); + return leave(); } private static boolean isLogical(final TokenType tt) { switch (tt) { - case AND: - case OR: - return true; - default: - return false; + case AND: + case OR: + return true; + default: + return false; } } @Override - public Node enterBinaryNode(final BinaryNode binaryNode) { + public boolean enterBinaryNode(final BinaryNode binaryNode) { enterDefault(binaryNode); final String name; @@ -179,29 +183,29 @@ public final class JSONWriter extends NodeVisitor { property("right"); binaryNode.rhs().accept(this); - return leaveDefault(binaryNode); + return leave(); } @Override - public Node enterBreakNode(final BreakNode breakNode) { + public boolean enterBreakNode(final BreakNode breakNode) { enterDefault(breakNode); type("BreakStatement"); comma(); - final LabelNode label = breakNode.getLabel(); + final IdentNode label = breakNode.getLabel(); if (label != null) { - property("label", label.getLabel().getName()); + property("label", label.getName()); } else { property("label"); nullValue(); } - return leaveDefault(breakNode); + return leave(); } @Override - public Node enterCallNode(final CallNode callNode) { + public boolean enterCallNode(final CallNode callNode) { enterDefault(callNode); type("CallExpression"); @@ -213,11 +217,11 @@ public final class JSONWriter extends NodeVisitor { array("arguments", callNode.getArgs()); - return leaveDefault(callNode); + return leave(); } @Override - public Node enterCaseNode(final CaseNode caseNode) { + public boolean enterCaseNode(final CaseNode caseNode) { enterDefault(caseNode); type("SwitchCase"); @@ -234,11 +238,11 @@ public final class JSONWriter extends NodeVisitor { array("consequent", caseNode.getBody().getStatements()); - return leaveDefault(caseNode); + return leave(); } @Override - public Node enterCatchNode(final CatchNode catchNode) { + public boolean enterCatchNode(final CatchNode catchNode) { enterDefault(catchNode); type("CatchClause"); @@ -260,55 +264,38 @@ public final class JSONWriter extends NodeVisitor { property("body"); catchNode.getBody().accept(this); - return leaveDefault(catchNode); + return leave(); } @Override - public Node enterContinueNode(final ContinueNode continueNode) { + public boolean enterContinueNode(final ContinueNode continueNode) { enterDefault(continueNode); type("ContinueStatement"); comma(); - final LabelNode label = continueNode.getLabel(); + final IdentNode label = continueNode.getLabel(); if (label != null) { - property("label", label.getLabel().getName()); + property("label", label.getName()); } else { property("label"); nullValue(); } - return leaveDefault(continueNode); + return leave(); } @Override - public Node enterDoWhileNode(final DoWhileNode doWhileNode) { - enterDefault(doWhileNode); - - type("DoWhileStatement"); - comma(); - - property("body"); - doWhileNode.getBody().accept(this); - comma(); - - property("test"); - doWhileNode.getTest().accept(this); - - return leaveDefault(doWhileNode); - } - - @Override - public Node enterEmptyNode(final EmptyNode emptyNode) { + public boolean enterEmptyNode(final EmptyNode emptyNode) { enterDefault(emptyNode); type("EmptyStatement"); - return leaveDefault(emptyNode); + return leave(); } @Override - public Node enterExecuteNode(final ExecuteNode executeNode) { + public boolean enterExecuteNode(final ExecuteNode executeNode) { enterDefault(executeNode); type("ExpressionStatement"); @@ -317,11 +304,11 @@ public final class JSONWriter extends NodeVisitor { property("expression"); executeNode.getExpression().accept(this); - return leaveDefault(executeNode); + return leave(); } @Override - public Node enterForNode(final ForNode forNode) { + public boolean enterForNode(final ForNode forNode) { enterDefault(forNode); if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) { @@ -380,11 +367,11 @@ public final class JSONWriter extends NodeVisitor { forNode.getBody().accept(this); } - return leaveDefault(forNode); + return leave(); } @Override - public Node enterFunctionNode(final FunctionNode functionNode) { + public boolean enterFunctionNode(final FunctionNode functionNode) { enterDefault(functionNode); final boolean program = functionNode.isProgram(); @@ -419,7 +406,7 @@ public final class JSONWriter extends NodeVisitor { } // body consists of nested functions and statements - final List<Node> stats = functionNode.getStatements(); + final List<Node> stats = functionNode.getBody().getStatements(); final int size = stats.size(); int idx = 0; arrayStart("body"); @@ -435,11 +422,11 @@ public final class JSONWriter extends NodeVisitor { } arrayEnd(); - return leaveDefault(functionNode); + return leave(); } @Override - public Node enterIdentNode(final IdentNode identNode) { + public boolean enterIdentNode(final IdentNode identNode) { enterDefault(identNode); final String name = identNode.getName(); @@ -451,11 +438,11 @@ public final class JSONWriter extends NodeVisitor { property("name", identNode.getName()); } - return leaveDefault(identNode); + return leave(); } @Override - public Node enterIfNode(final IfNode ifNode) { + public boolean enterIfNode(final IfNode ifNode) { enterDefault(ifNode); type("IfStatement"); @@ -477,11 +464,11 @@ public final class JSONWriter extends NodeVisitor { nullValue(); } - return leaveDefault(ifNode); + return leave(); } @Override - public Node enterIndexNode(final IndexNode indexNode) { + public boolean enterIndexNode(final IndexNode indexNode) { enterDefault(indexNode); type("MemberExpression"); @@ -497,11 +484,11 @@ public final class JSONWriter extends NodeVisitor { property("computed", true); - return leaveDefault(indexNode); + return leave(); } @Override - public Node enterLabelNode(final LabelNode labelNode) { + public boolean enterLabelNode(final LabelNode labelNode) { enterDefault(labelNode); type("LabeledStatement"); @@ -514,17 +501,17 @@ public final class JSONWriter extends NodeVisitor { property("body"); labelNode.getBody().accept(this); - return leaveDefault(labelNode); + return leave(); } @Override - public Node enterLineNumberNode(final LineNumberNode lineNumberNode) { - return null; + public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) { + return false; } @SuppressWarnings("rawtypes") @Override - public Node enterLiteralNode(final LiteralNode literalNode) { + public boolean enterLiteralNode(final LiteralNode literalNode) { enterDefault(literalNode); if (literalNode instanceof LiteralNode.ArrayLiteralNode) { @@ -556,11 +543,11 @@ public final class JSONWriter extends NodeVisitor { } } - return leaveDefault(literalNode); + return leave(); } @Override - public Node enterObjectNode(final ObjectNode objectNode) { + public boolean enterObjectNode(final ObjectNode objectNode) { enterDefault(objectNode); type("ObjectExpression"); @@ -568,11 +555,11 @@ public final class JSONWriter extends NodeVisitor { array("properties", objectNode.getElements()); - return leaveDefault(objectNode); + return leave(); } @Override - public Node enterPropertyNode(final PropertyNode propertyNode) { + public boolean enterPropertyNode(final PropertyNode propertyNode) { final Node key = propertyNode.getKey(); final Node value = propertyNode.getValue(); @@ -634,11 +621,11 @@ public final class JSONWriter extends NodeVisitor { } } - return null; + return false; } @Override - public Node enterReturnNode(final ReturnNode returnNode) { + public boolean enterReturnNode(final ReturnNode returnNode) { enterDefault(returnNode); type("ReturnStatement"); @@ -652,31 +639,29 @@ public final class JSONWriter extends NodeVisitor { nullValue(); } - return leaveDefault(returnNode); + return leave(); } @Override - public Node enterRuntimeNode(final RuntimeNode runtimeNode) { + public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { final RuntimeNode.Request req = runtimeNode.getRequest(); if (req == RuntimeNode.Request.DEBUGGER) { enterDefault(runtimeNode); - type("DebuggerStatement"); - - return leaveDefault(runtimeNode); + return leave(); } - return null; + return false; } @Override - public Node enterSplitNode(final SplitNode splitNode) { - return null; + public boolean enterSplitNode(final SplitNode splitNode) { + return false; } @Override - public Node enterSwitchNode(final SwitchNode switchNode) { + public boolean enterSwitchNode(final SwitchNode switchNode) { enterDefault(switchNode); type("SwitchStatement"); @@ -688,11 +673,11 @@ public final class JSONWriter extends NodeVisitor { array("cases", switchNode.getCases()); - return leaveDefault(switchNode); + return leave(); } @Override - public Node enterTernaryNode(final TernaryNode ternaryNode) { + public boolean enterTernaryNode(final TernaryNode ternaryNode) { enterDefault(ternaryNode); type("ConditionalExpression"); @@ -709,11 +694,11 @@ public final class JSONWriter extends NodeVisitor { property("alternate"); ternaryNode.third().accept(this); - return leaveDefault(ternaryNode); + return leave(); } @Override - public Node enterThrowNode(final ThrowNode throwNode) { + public boolean enterThrowNode(final ThrowNode throwNode) { enterDefault(throwNode); type("ThrowStatement"); @@ -722,11 +707,11 @@ public final class JSONWriter extends NodeVisitor { property("argument"); throwNode.getExpression().accept(this); - return leaveDefault(throwNode); + return leave(); } @Override - public Node enterTryNode(final TryNode tryNode) { + public boolean enterTryNode(final TryNode tryNode) { enterDefault(tryNode); type("TryStatement"); @@ -747,11 +732,11 @@ public final class JSONWriter extends NodeVisitor { nullValue(); } - return leaveDefault(tryNode); + return leave(); } @Override - public Node enterUnaryNode(final UnaryNode unaryNode) { + public boolean enterUnaryNode(final UnaryNode unaryNode) { enterDefault(unaryNode); final TokenType tokenType = unaryNode.tokenType(); @@ -769,25 +754,25 @@ public final class JSONWriter extends NodeVisitor { final boolean prefix; final String operator; switch (tokenType) { - case INCPOSTFIX: - prefix = false; - operator = "++"; - break; - case DECPOSTFIX: - prefix = false; - operator = "--"; - break; - case INCPREFIX: - operator = "++"; - prefix = true; - break; - case DECPREFIX: - operator = "--"; - prefix = true; - break; - default: - prefix = false; - operator = tokenType.getName(); + case INCPOSTFIX: + prefix = false; + operator = "++"; + break; + case DECPOSTFIX: + prefix = false; + operator = "--"; + break; + case INCPREFIX: + operator = "++"; + prefix = true; + break; + case DECPREFIX: + operator = "--"; + prefix = true; + break; + default: + prefix = false; + operator = tokenType.getName(); } type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression"); @@ -803,11 +788,11 @@ public final class JSONWriter extends NodeVisitor { unaryNode.rhs().accept(this); } - return leaveDefault(unaryNode); + return leave(); } @Override - public Node enterVarNode(final VarNode varNode) { + public boolean enterVarNode(final VarNode varNode) { enterDefault(varNode); type("VariableDeclaration"); @@ -839,28 +824,37 @@ public final class JSONWriter extends NodeVisitor { // declarations arrayEnd(); - return leaveDefault(varNode); + return leave(); } @Override - public Node enterWhileNode(final WhileNode whileNode) { + public boolean enterWhileNode(final WhileNode whileNode) { enterDefault(whileNode); - type("WhileStatement"); + type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement"); comma(); - property("test"); - whileNode.getTest().accept(this); - comma(); + if (whileNode.isDoWhile()) { + property("body"); + whileNode.getBody().accept(this); + comma(); - property("block"); - whileNode.getBody().accept(this); + property("test"); + whileNode.getTest().accept(this); + } else { + property("test"); + whileNode.getTest().accept(this); + comma(); + + property("block"); + whileNode.getBody().accept(this); + } - return leaveDefault(whileNode); + return leave(); } @Override - public Node enterWithNode(final WithNode withNode) { + public boolean enterWithNode(final WithNode withNode) { enterDefault(withNode); type("WithStatement"); @@ -873,8 +867,8 @@ public final class JSONWriter extends NodeVisitor { property("body"); withNode.getBody().accept(this); - return leaveDefault(withNode); - } + return leave(); + } // Internals below diff --git a/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java b/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java index d2f40d1a..8637b66d 100644 --- a/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java +++ b/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java @@ -26,30 +26,22 @@ package jdk.nashorn.internal.ir.debug; import java.util.List; -import jdk.nashorn.internal.ir.AccessNode; + +import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; -import jdk.nashorn.internal.ir.BreakNode; -import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; -import jdk.nashorn.internal.ir.ContinueNode; -import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.IfNode; -import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.LabelNode; import jdk.nashorn.internal.ir.LineNumberNode; import jdk.nashorn.internal.ir.Node; -import jdk.nashorn.internal.ir.ReturnNode; -import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.SplitNode; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; -import jdk.nashorn.internal.ir.ThrowNode; import jdk.nashorn.internal.ir.TryNode; -import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.VarNode; import jdk.nashorn.internal.ir.WhileNode; import jdk.nashorn.internal.ir.WithNode; @@ -136,21 +128,20 @@ public final class PrintVisitor extends NodeVisitor { /* * Visits. */ + @Override - public Node enterAccessNode(final AccessNode accessNode) { - accessNode.toString(sb); - return null; + public boolean enterDefault(final Node node) { + node.toString(sb); + return false; } @Override - public Node enterBlock(final Block block) { + public boolean enterBlock(final Block block) { sb.append(' '); sb.append('{'); indent += TABWIDTH; - final boolean isFunction = block instanceof FunctionNode; - final List<Node> statements = block.getStatements(); boolean lastLineNumber = false; @@ -161,14 +152,14 @@ public final class PrintVisitor extends NodeVisitor { indent(); } - if (statement instanceof UnaryNode) { - statement.toString(sb); - } else { - statement.accept(this); - } + statement.accept(this); lastLineNumber = statement instanceof LineNumberNode; + if (statement instanceof FunctionNode) { + continue; + } + final Symbol symbol = statement.getSymbol(); if (symbol != null) { @@ -200,72 +191,42 @@ public final class PrintVisitor extends NodeVisitor { indent(); sb.append("}"); - if (isFunction) { - sb.append(EOLN); - } - - return null; - } - - @Override - public Node enterBreakNode(final BreakNode breakNode) { - breakNode.toString(sb); - return null; - } - - @Override - public Node enterCallNode(final CallNode callNode) { - callNode.toString(sb); - return null; + return false; } @Override - public Node enterContinueNode(final ContinueNode continueNode) { - continueNode.toString(sb); - return null; - } - - @Override - public Node enterDoWhileNode(final DoWhileNode doWhileNode) { - sb.append("do"); - doWhileNode.getBody().accept(this); + public boolean enterBinaryNode(final BinaryNode binaryNode) { + binaryNode.lhs().accept(this); sb.append(' '); - doWhileNode.toString(sb); - - return null; + sb.append(binaryNode.tokenType()); + sb.append(' '); + binaryNode.rhs().accept(this); + return false; } @Override - public Node enterExecuteNode(final ExecuteNode executeNode) { - final Node expression = executeNode.getExpression(); - - if (expression instanceof UnaryNode) { - expression.toString(sb); - } else { - expression.accept(this); - } - - return null; + public boolean enterExecuteNode(final ExecuteNode executeNode) { + executeNode.getExpression().accept(this); + return false; } @Override - public Node enterForNode(final ForNode forNode) { + public boolean enterForNode(final ForNode forNode) { forNode.toString(sb); forNode.getBody().accept(this); - - return null; + return false; } @Override - public Node enterFunctionNode(final FunctionNode functionNode) { + public boolean enterFunctionNode(final FunctionNode functionNode) { functionNode.toString(sb); - enterBlock(functionNode); - - return null; + enterBlock(functionNode.getBody()); + sb.append(EOLN); + return false; } @Override - public Node enterIfNode(final IfNode ifNode) { + public boolean enterIfNode(final IfNode ifNode) { ifNode.toString(sb); ifNode.getPass().accept(this); @@ -276,55 +237,36 @@ public final class PrintVisitor extends NodeVisitor { fail.accept(this); } - return null; + return false; } @Override - public Node enterIndexNode(final IndexNode indexNode) { - indexNode.toString(sb); - return null; - } - - @Override - public Node enterLabelNode(final LabelNode labeledNode) { + public boolean enterLabelNode(final LabelNode labeledNode) { indent -= TABWIDTH; indent(); indent += TABWIDTH; labeledNode.toString(sb); labeledNode.getBody().accept(this); - return null; + return false; } @Override - public Node enterLineNumberNode(final LineNumberNode lineNumberNode) { + public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) { if (printLineNumbers) { lineNumberNode.toString(sb); } - return null; + return false; } - @Override - public Node enterReturnNode(final ReturnNode returnNode) { - returnNode.toString(sb); - return null; - } - - @Override - public Node enterRuntimeNode(final RuntimeNode runtimeNode) { - runtimeNode.toString(sb); - return null; - } - - @Override - public Node enterSplitNode(final SplitNode splitNode) { + public boolean enterSplitNode(final SplitNode splitNode) { splitNode.toString(sb); sb.append(EOLN); indent += TABWIDTH; indent(); - return splitNode; + return true; } @Override @@ -337,7 +279,7 @@ public final class PrintVisitor extends NodeVisitor { } @Override - public Node enterSwitchNode(final SwitchNode switchNode) { + public boolean enterSwitchNode(final SwitchNode switchNode) { switchNode.toString(sb); sb.append(" {"); @@ -357,24 +299,18 @@ public final class PrintVisitor extends NodeVisitor { indent(); sb.append("}"); - return null; - } - - @Override - public Node enterThrowNode(final ThrowNode throwNode) { - throwNode.toString(sb); - return null; + return false; } @Override - public Node enterTryNode(final TryNode tryNode) { + public boolean enterTryNode(final TryNode tryNode) { tryNode.toString(sb); tryNode.getBody().accept(this); final List<Block> catchBlocks = tryNode.getCatchBlocks(); for (final Block catchBlock : catchBlocks) { - final CatchNode catchNode = (CatchNode) catchBlock.getStatements().get(0); + final CatchNode catchNode = (CatchNode)catchBlock.getStatements().get(0); catchNode.toString(sb); catchNode.getBody().accept(this); } @@ -386,35 +322,42 @@ public final class PrintVisitor extends NodeVisitor { finallyBody.accept(this); } - return null; + return false; } @Override - public Node enterVarNode(final VarNode varNode) { + public boolean enterVarNode(final VarNode varNode) { sb.append("var "); varNode.getName().toString(sb); final Node init = varNode.getInit(); - if(init != null) { + if (init != null) { sb.append(" = "); init.accept(this); } - return null; + return false; } @Override - public Node enterWhileNode(final WhileNode whileNode) { - whileNode.toString(sb); - whileNode.getBody().accept(this); + public boolean enterWhileNode(final WhileNode whileNode) { + if (whileNode.isDoWhile()) { + sb.append("do"); + whileNode.getBody().accept(this); + sb.append(' '); + whileNode.toString(sb); + } else { + whileNode.toString(sb); + whileNode.getBody().accept(this); + } - return null; + return false; } @Override - public Node enterWithNode(final WithNode withNode) { + public boolean enterWithNode(final WithNode withNode) { withNode.toString(sb); withNode.getBody().accept(this); - return null; + return false; } } diff --git a/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java b/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java index 0021b7d2..4f128412 100644 --- a/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java +++ b/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java @@ -25,9 +25,8 @@ package jdk.nashorn.internal.ir.visitor; -import jdk.nashorn.internal.codegen.CompileUnit; -import jdk.nashorn.internal.codegen.MethodEmitter; import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.UnaryNode; @@ -45,15 +44,14 @@ public class NodeOperatorVisitor extends NodeVisitor { /** * Constructor * - * @param compileUnit compile unit - * @param method method emitter + * @param lc a custom lexical context */ - public NodeOperatorVisitor(final CompileUnit compileUnit, final MethodEmitter method) { - super(compileUnit, method); + public NodeOperatorVisitor(final LexicalContext lc) { + super(lc); } @Override - public final Node enterUnaryNode(final UnaryNode unaryNode) { + public final boolean enterUnaryNode(final UnaryNode unaryNode) { switch (unaryNode.tokenType()) { case ADD: return enterADD(unaryNode); @@ -119,7 +117,7 @@ public class NodeOperatorVisitor extends NodeVisitor { } @Override - public final Node enterBinaryNode(final BinaryNode binaryNode) { + public final boolean enterBinaryNode(final BinaryNode binaryNode) { switch (binaryNode.tokenType()) { case ADD: return enterADD(binaryNode); @@ -287,17 +285,6 @@ public class NodeOperatorVisitor extends NodeVisitor { } /* - @Override - public Node enter(final TernaryNode ternaryNode) { - return enterDefault(ternaryNode); - } - - @Override - public Node leave(final TernaryNode ternaryNode) { - return leaveDefault(ternaryNode); - }*/ - - /* * Unary entries and exists. */ @@ -305,9 +292,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a unary + * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterADD(final UnaryNode unaryNode) { + public boolean enterADD(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -325,9 +312,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a ~ operator * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterBIT_NOT(final UnaryNode unaryNode) { + public boolean enterBIT_NOT(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -345,9 +332,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a conversion * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterCONVERT(final UnaryNode unaryNode) { + public boolean enterCONVERT(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -365,9 +352,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a ++ or -- operator * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterDECINC(final UnaryNode unaryNode) { + public boolean enterDECINC(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -387,7 +374,7 @@ public class NodeOperatorVisitor extends NodeVisitor { * @param unaryNode the node * @return processed node */ - public Node enterDELETE(final UnaryNode unaryNode) { + public boolean enterDELETE(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -405,9 +392,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a discard operator * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterDISCARD(final UnaryNode unaryNode) { + public boolean enterDISCARD(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -425,9 +412,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a new operator * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterNEW(final UnaryNode unaryNode) { + public boolean enterNEW(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -445,9 +432,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a ! operator * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterNOT(final UnaryNode unaryNode) { + public boolean enterNOT(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -465,9 +452,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a unary - * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterSUB(final UnaryNode unaryNode) { + public boolean enterSUB(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -485,9 +472,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a typeof * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterTYPEOF(final UnaryNode unaryNode) { + public boolean enterTYPEOF(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -505,9 +492,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Unary enter - callback for entering a void * * @param unaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterVOID(final UnaryNode unaryNode) { + public boolean enterVOID(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -525,9 +512,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering + operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterADD(final BinaryNode binaryNode) { + public boolean enterADD(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -545,9 +532,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal &&} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterAND(final BinaryNode binaryNode) { + public boolean enterAND(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -565,9 +552,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering an assignment * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN(final BinaryNode binaryNode) { + public boolean enterASSIGN(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -585,9 +572,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering += operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_ADD(final BinaryNode binaryNode) { + public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -605,9 +592,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal &=} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_BIT_AND(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -625,9 +612,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering |= operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_BIT_OR(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -645,9 +632,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering ^= operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { + public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -665,9 +652,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering /= operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_DIV(final BinaryNode binaryNode) { + public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -685,9 +672,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering %= operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_MOD(final BinaryNode binaryNode) { + public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -705,9 +692,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering *= operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_MUL(final BinaryNode binaryNode) { + public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -725,9 +712,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal >>=} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_SAR(final BinaryNode binaryNode) { + public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -745,9 +732,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering a {@literal <<=} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_SHL(final BinaryNode binaryNode) { + public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -765,9 +752,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal >>>=} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_SHR(final BinaryNode binaryNode) { + public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -785,9 +772,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering -= operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterASSIGN_SUB(final BinaryNode binaryNode) { + public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -805,9 +792,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering a bind operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterBIND(final BinaryNode binaryNode) { + public boolean enterBIND(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -825,9 +812,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal &} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterBIT_AND(final BinaryNode binaryNode) { + public boolean enterBIT_AND(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -845,9 +832,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering | operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterBIT_OR(final BinaryNode binaryNode) { + public boolean enterBIT_OR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -865,9 +852,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering ^ operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterBIT_XOR(final BinaryNode binaryNode) { + public boolean enterBIT_XOR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -886,9 +873,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * (a, b) where the result is a * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterCOMMALEFT(final BinaryNode binaryNode) { + public boolean enterCOMMALEFT(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -908,9 +895,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * (a, b) where the result is b * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterCOMMARIGHT(final BinaryNode binaryNode) { + public boolean enterCOMMARIGHT(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -929,9 +916,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering a division * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterDIV(final BinaryNode binaryNode) { + public boolean enterDIV(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -949,9 +936,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering == operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterEQ(final BinaryNode binaryNode) { + public boolean enterEQ(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -969,9 +956,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering === operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterEQ_STRICT(final BinaryNode binaryNode) { + public boolean enterEQ_STRICT(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -989,9 +976,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal >=} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterGE(final BinaryNode binaryNode) { + public boolean enterGE(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1009,9 +996,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal >} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterGT(final BinaryNode binaryNode) { + public boolean enterGT(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1029,9 +1016,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering in operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterIN(final BinaryNode binaryNode) { + public boolean enterIN(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1049,9 +1036,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering instanceof operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterINSTANCEOF(final BinaryNode binaryNode) { + public boolean enterINSTANCEOF(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1069,9 +1056,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal <=} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterLE(final BinaryNode binaryNode) { + public boolean enterLE(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1089,9 +1076,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal <} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterLT(final BinaryNode binaryNode) { + public boolean enterLT(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1108,9 +1095,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering % operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterMOD(final BinaryNode binaryNode) { + public boolean enterMOD(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1128,9 +1115,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering * operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterMUL(final BinaryNode binaryNode) { + public boolean enterMUL(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1148,9 +1135,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering != operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterNE(final BinaryNode binaryNode) { + public boolean enterNE(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1168,9 +1155,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering a !== operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterNE_STRICT(final BinaryNode binaryNode) { + public boolean enterNE_STRICT(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1188,9 +1175,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering || operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterOR(final BinaryNode binaryNode) { + public boolean enterOR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1208,9 +1195,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal >>} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterSAR(final BinaryNode binaryNode) { + public boolean enterSAR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1228,9 +1215,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal <<} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterSHL(final BinaryNode binaryNode) { + public boolean enterSHL(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1247,9 +1234,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering {@literal >>>} operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterSHR(final BinaryNode binaryNode) { + public boolean enterSHR(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -1267,9 +1254,9 @@ public class NodeOperatorVisitor extends NodeVisitor { * Binary enter - callback for entering - operator * * @param binaryNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterSUB(final BinaryNode binaryNode) { + public boolean enterSUB(final BinaryNode binaryNode) { return enterDefault(binaryNode); } diff --git a/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java b/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java index f10d8c03..e3c0d34e 100644 --- a/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java +++ b/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java @@ -25,8 +25,6 @@ package jdk.nashorn.internal.ir.visitor; -import jdk.nashorn.internal.codegen.CompileUnit; -import jdk.nashorn.internal.codegen.MethodEmitter; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; @@ -35,7 +33,6 @@ import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; -import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.EmptyNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; @@ -44,6 +41,7 @@ import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.LabelNode; +import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LineNumberNode; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.Node; @@ -65,41 +63,30 @@ import jdk.nashorn.internal.ir.WithNode; * Visitor used to navigate the IR. */ public abstract class NodeVisitor { - /** Current functionNode. */ - private FunctionNode currentFunctionNode; - - /** Current compile unit used for class generation. */ - private CompileUnit compileUnit; - - /** - * Current method visitor used for method generation. - * <p> - * TODO: protected is just for convenience and readability, so that - * subclasses can directly use 'method' - might want to change that - */ - protected MethodEmitter method; - - /** Current block. */ - private Block currentBlock; + private final LexicalContext lc; /** - * Constructor. + * Constructor */ public NodeVisitor() { - this(null, null); + this(new LexicalContext()); } /** * Constructor * - * @param compileUnit compile unit for this node visitor - * @param method method emitter for this node visitor + * @param lc a custom lexical context */ - public NodeVisitor(final CompileUnit compileUnit, final MethodEmitter method) { - super(); + public NodeVisitor(final LexicalContext lc) { + this.lc = lc; + } - this.compileUnit = compileUnit; - this.method = method; + /** + * Get the lexical context of this node visitor + * @return lexical context + */ + public LexicalContext getLexicalContext() { + return lc; } /** @@ -118,10 +105,10 @@ public abstract class NodeVisitor { * * @see NodeVisitor#leaveDefault(Node) * @param node the node to visit - * @return the node + * @return true if traversal should continue and node children be traversed, false otherwise */ - protected Node enterDefault(final Node node) { - return node; + protected boolean enterDefault(final Node node) { + return true; } /** @@ -150,9 +137,9 @@ public abstract class NodeVisitor { * Callback for entering an AccessNode * * @param accessNode the node - * @return processed node, null if traversal should end, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterAccessNode(final AccessNode accessNode) { + public boolean enterAccessNode(final AccessNode accessNode) { return enterDefault(accessNode); } @@ -170,9 +157,9 @@ public abstract class NodeVisitor { * Callback for entering a Block * * @param block the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterBlock(final Block block) { + public boolean enterBlock(final Block block) { return enterDefault(block); } @@ -192,7 +179,7 @@ public abstract class NodeVisitor { * @param binaryNode the node * @return processed node */ - public Node enterBinaryNode(final BinaryNode binaryNode) { + public boolean enterBinaryNode(final BinaryNode binaryNode) { return enterDefault(binaryNode); } @@ -210,9 +197,9 @@ public abstract class NodeVisitor { * Callback for entering a BreakNode * * @param breakNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterBreakNode(final BreakNode breakNode) { + public boolean enterBreakNode(final BreakNode breakNode) { return enterDefault(breakNode); } @@ -230,9 +217,9 @@ public abstract class NodeVisitor { * Callback for entering a CallNode * * @param callNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterCallNode(final CallNode callNode) { + public boolean enterCallNode(final CallNode callNode) { return enterDefault(callNode); } @@ -250,9 +237,9 @@ public abstract class NodeVisitor { * Callback for entering a CaseNode * * @param caseNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterCaseNode(final CaseNode caseNode) { + public boolean enterCaseNode(final CaseNode caseNode) { return enterDefault(caseNode); } @@ -270,9 +257,9 @@ public abstract class NodeVisitor { * Callback for entering a CatchNode * * @param catchNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterCatchNode(final CatchNode catchNode) { + public boolean enterCatchNode(final CatchNode catchNode) { return enterDefault(catchNode); } @@ -290,9 +277,9 @@ public abstract class NodeVisitor { * Callback for entering a ContinueNode * * @param continueNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterContinueNode(final ContinueNode continueNode) { + public boolean enterContinueNode(final ContinueNode continueNode) { return enterDefault(continueNode); } @@ -307,32 +294,12 @@ public abstract class NodeVisitor { } /** - * Callback for entering a DoWhileNode - * - * @param doWhileNode the node - * @return processed node - */ - public Node enterDoWhileNode(final DoWhileNode doWhileNode) { - return enterDefault(doWhileNode); - } - - /** - * Callback for leaving a DoWhileNode - * - * @param doWhileNode the node - * @return processed node, which will replace the original one, or the original node - */ - public Node leaveDoWhileNode(final DoWhileNode doWhileNode) { - return leaveDefault(doWhileNode); - } - - /** * Callback for entering an EmptyNode * * @param emptyNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterEmptyNode(final EmptyNode emptyNode) { + public boolean enterEmptyNode(final EmptyNode emptyNode) { return enterDefault(emptyNode); } @@ -350,9 +317,9 @@ public abstract class NodeVisitor { * Callback for entering an ExecuteNode * * @param executeNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterExecuteNode(final ExecuteNode executeNode) { + public boolean enterExecuteNode(final ExecuteNode executeNode) { return enterDefault(executeNode); } @@ -370,9 +337,9 @@ public abstract class NodeVisitor { * Callback for entering a ForNode * * @param forNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterForNode(final ForNode forNode) { + public boolean enterForNode(final ForNode forNode) { return enterDefault(forNode); } @@ -390,9 +357,9 @@ public abstract class NodeVisitor { * Callback for entering a FunctionNode * * @param functionNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterFunctionNode(final FunctionNode functionNode) { + public boolean enterFunctionNode(final FunctionNode functionNode) { return enterDefault(functionNode); } @@ -410,9 +377,9 @@ public abstract class NodeVisitor { * Callback for entering an IdentNode * * @param identNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterIdentNode(final IdentNode identNode) { + public boolean enterIdentNode(final IdentNode identNode) { return enterDefault(identNode); } @@ -429,10 +396,10 @@ public abstract class NodeVisitor { /** * Callback for entering an IfNode * - * @param ifNode the node - * @return processed node, null if traversal should end + * @param ifNode the node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterIfNode(final IfNode ifNode) { + public boolean enterIfNode(final IfNode ifNode) { return enterDefault(ifNode); } @@ -450,9 +417,9 @@ public abstract class NodeVisitor { * Callback for entering an IndexNode * * @param indexNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterIndexNode(final IndexNode indexNode) { + public boolean enterIndexNode(final IndexNode indexNode) { return enterDefault(indexNode); } @@ -470,9 +437,9 @@ public abstract class NodeVisitor { * Callback for entering a LabelNode * * @param labelNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterLabelNode(final LabelNode labelNode) { + public boolean enterLabelNode(final LabelNode labelNode) { return enterDefault(labelNode); } @@ -490,9 +457,9 @@ public abstract class NodeVisitor { * Callback for entering a LineNumberNode * * @param lineNumberNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterLineNumberNode(final LineNumberNode lineNumberNode) { + public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) { return enterDefault(lineNumberNode); } @@ -510,9 +477,9 @@ public abstract class NodeVisitor { * Callback for entering a LiteralNode * * @param literalNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterLiteralNode(final LiteralNode<?> literalNode) { + public boolean enterLiteralNode(final LiteralNode<?> literalNode) { return enterDefault(literalNode); } @@ -530,9 +497,9 @@ public abstract class NodeVisitor { * Callback for entering an ObjectNode * * @param objectNode the node - * @return processed node + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterObjectNode(final ObjectNode objectNode) { + public boolean enterObjectNode(final ObjectNode objectNode) { return enterDefault(objectNode); } @@ -550,9 +517,9 @@ public abstract class NodeVisitor { * Callback for entering a PropertyNode * * @param propertyNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterPropertyNode(final PropertyNode propertyNode) { + public boolean enterPropertyNode(final PropertyNode propertyNode) { return enterDefault(propertyNode); } @@ -570,9 +537,9 @@ public abstract class NodeVisitor { * Callback for entering a ReturnNode * * @param returnNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterReturnNode(final ReturnNode returnNode) { + public boolean enterReturnNode(final ReturnNode returnNode) { return enterDefault(returnNode); } @@ -590,9 +557,9 @@ public abstract class NodeVisitor { * Callback for entering a RuntimeNode * * @param runtimeNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterRuntimeNode(final RuntimeNode runtimeNode) { + public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { return enterDefault(runtimeNode); } @@ -610,9 +577,9 @@ public abstract class NodeVisitor { * Callback for entering a SplitNode * * @param splitNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterSplitNode(final SplitNode splitNode) { + public boolean enterSplitNode(final SplitNode splitNode) { return enterDefault(splitNode); } @@ -630,9 +597,9 @@ public abstract class NodeVisitor { * Callback for entering a SwitchNode * * @param switchNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterSwitchNode(final SwitchNode switchNode) { + public boolean enterSwitchNode(final SwitchNode switchNode) { return enterDefault(switchNode); } @@ -650,9 +617,9 @@ public abstract class NodeVisitor { * Callback for entering a TernaryNode * * @param ternaryNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterTernaryNode(final TernaryNode ternaryNode) { + public boolean enterTernaryNode(final TernaryNode ternaryNode) { return enterDefault(ternaryNode); } @@ -670,9 +637,9 @@ public abstract class NodeVisitor { * Callback for entering a ThrowNode * * @param throwNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterThrowNode(final ThrowNode throwNode) { + public boolean enterThrowNode(final ThrowNode throwNode) { return enterDefault(throwNode); } @@ -690,9 +657,9 @@ public abstract class NodeVisitor { * Callback for entering a TryNode * * @param tryNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterTryNode(final TryNode tryNode) { + public boolean enterTryNode(final TryNode tryNode) { return enterDefault(tryNode); } @@ -710,9 +677,9 @@ public abstract class NodeVisitor { * Callback for entering a UnaryNode * * @param unaryNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterUnaryNode(final UnaryNode unaryNode) { + public boolean enterUnaryNode(final UnaryNode unaryNode) { return enterDefault(unaryNode); } @@ -730,9 +697,9 @@ public abstract class NodeVisitor { * Callback for entering a VarNode * * @param varNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterVarNode(final VarNode varNode) { + public boolean enterVarNode(final VarNode varNode) { return enterDefault(varNode); } @@ -750,9 +717,9 @@ public abstract class NodeVisitor { * Callback for entering a WhileNode * * @param whileNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterWhileNode(final WhileNode whileNode) { + public boolean enterWhileNode(final WhileNode whileNode) { return enterDefault(whileNode); } @@ -770,9 +737,9 @@ public abstract class NodeVisitor { * Callback for entering a WithNode * * @param withNode the node - * @return processed node, null if traversal should end + * @return true if traversal should continue and node children be traversed, false otherwise */ - public Node enterWithNode(final WithNode withNode) { + public boolean enterWithNode(final WithNode withNode) { return enterDefault(withNode); } @@ -786,74 +753,5 @@ public abstract class NodeVisitor { return leaveDefault(withNode); } - /** - * Get the current function node for this NodeVisitor - * @see FunctionNode - * @return the function node being visited - */ - public FunctionNode getCurrentFunctionNode() { - return currentFunctionNode; - } - - /** - * Reset the current function node being visited for this NodeVisitor - * @see FunctionNode - * @param currentFunctionNode a new function node to traverse - */ - public void setCurrentFunctionNode(final FunctionNode currentFunctionNode) { - this.currentFunctionNode = currentFunctionNode; - } - - /** - * Get the current compile unit for this NodeVisitor - * @see CompileUnit - * @return a compile unit, or null if not a compiling NodeVisitor - */ - public CompileUnit getCurrentCompileUnit() { - return compileUnit; - } - - /** - * Set the current compile unit for this NodeVisitor - * @see CompileUnit - * @param compileUnit a new compile unit - */ - public void setCurrentCompileUnit(final CompileUnit compileUnit) { - this.compileUnit = compileUnit; - } - - /** - * Get the current method emitter for this NodeVisitor - * @see MethodEmitter - * @return the method emitter - */ - public MethodEmitter getCurrentMethodEmitter() { - return method; - } - - /** - * Reset the current method emitter for this NodeVisitor - * @see MethodEmitter - * @param method a new method emitter - */ - public void setCurrentMethodEmitter(final MethodEmitter method) { - this.method = method; - } - - /** - * Get the current Block being traversed for this NodeVisitor - * @return the current block - */ - public Block getCurrentBlock() { - return currentBlock; - } - - /** - * Reset the Block to be traversed for this NodeVisitor - * @param currentBlock the new current block - */ - public void setCurrentBlock(final Block currentBlock) { - this.currentBlock = currentBlock; - } } diff --git a/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java b/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java index 2161c782..4d8a2977 100644 --- a/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java +++ b/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java @@ -137,7 +137,7 @@ public final class MethodHandleFactory { */ static Object traceReturn(final DebugLogger logger, final Object value) { final String str = "\treturn: " + stripName(value) + " [type=" + (value == null ? "null" : stripName(value.getClass()) + ']'); - logger.log(str, TRACE_LEVEL); + logger.log(TRACE_LEVEL, str); return value; } @@ -173,7 +173,7 @@ public final class MethodHandleFactory { } assert logger != null; - logger.log(sb.toString(), TRACE_LEVEL); + logger.log(TRACE_LEVEL, sb); stacktrace(logger); } @@ -184,7 +184,7 @@ public final class MethodHandleFactory { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(baos); new Throwable().printStackTrace(ps); - logger.log(baos.toString(), TRACE_LEVEL); + logger.log(TRACE_LEVEL, baos.toString()); } private static String argString(final Object arg) { @@ -614,7 +614,7 @@ public final class MethodHandleFactory { @Override public SwitchPoint createSwitchPoint() { final SwitchPoint sp = super.createSwitchPoint(); - LOG.log("createSwitchPoint " + sp, TRACE_LEVEL); + LOG.log(TRACE_LEVEL, "createSwitchPoint ", sp); return sp; } @@ -627,7 +627,7 @@ public final class MethodHandleFactory { @Override public MethodType type(final Class<?> returnType, final Class<?>... paramTypes) { final MethodType mt = super.type(returnType, paramTypes); - LOG.log("methodType " + returnType + ' ' + Arrays.toString(paramTypes) + ' ' + mt, TRACE_LEVEL); + LOG.log(TRACE_LEVEL, "methodType ", returnType, " ", Arrays.toString(paramTypes), " ", mt); return mt; } } @@ -638,7 +638,7 @@ public final class MethodHandleFactory { private static class TraceCreateMethodHandleFunctionality extends TraceMethodHandleFunctionality { @Override public MethodHandle debug(final MethodHandle master, final String str, final Object... args) { - LOG.log(str + ' ' + describe(args), TRACE_LEVEL); + LOG.log(TRACE_LEVEL, str, " ", describe(args)); stacktrace(LOG); return master; } diff --git a/src/jdk/nashorn/internal/objects/BoundScriptFunctionImpl.java b/src/jdk/nashorn/internal/objects/BoundScriptFunctionImpl.java index 1e9e9d18..be155f50 100644 --- a/src/jdk/nashorn/internal/objects/BoundScriptFunctionImpl.java +++ b/src/jdk/nashorn/internal/objects/BoundScriptFunctionImpl.java @@ -40,7 +40,7 @@ class BoundScriptFunctionImpl extends ScriptFunctionImpl { BoundScriptFunctionImpl(ScriptFunctionData data, ScriptFunction targetFunction) { super(data); - this.prototype = ScriptRuntime.UNDEFINED; + setPrototype(ScriptRuntime.UNDEFINED); this.targetFunction = targetFunction; } diff --git a/src/jdk/nashorn/internal/objects/NativeArray.java b/src/jdk/nashorn/internal/objects/NativeArray.java index 16e237ba..a659af4f 100644 --- a/src/jdk/nashorn/internal/objects/NativeArray.java +++ b/src/jdk/nashorn/internal/objects/NativeArray.java @@ -76,7 +76,7 @@ public final class NativeArray extends ScriptObject { private static final MethodHandle REDUCE_CALLBACK_INVOKER = Bootstrap.createDynamicInvoker("dyn:call", Object.class, Object.class, Undefined.class, Object.class, Object.class, int.class, Object.class); - private static final MethodHandle CALL_CMP = Bootstrap.createDynamicInvoker("dyn:call", int.class, + private static final MethodHandle CALL_CMP = Bootstrap.createDynamicInvoker("dyn:call", double.class, ScriptFunction.class, Object.class, Object.class, Object.class); private static final InvokeByName TO_LOCALE_STRING = new InvokeByName("toLocaleString", ScriptObject.class, String.class); @@ -160,7 +160,7 @@ public final class NativeArray extends ScriptObject { if ("length".equals(key)) { // Step 3a if (!desc.has(VALUE)) { - return super.defineOwnProperty("length", propertyDesc, reject); + return super.defineOwnProperty("length", desc, reject); } // Step 3b @@ -242,7 +242,7 @@ public final class NativeArray extends ScriptObject { // Step 4c // set the new array element - final boolean succeeded = super.defineOwnProperty(key, propertyDesc, false); + final boolean succeeded = super.defineOwnProperty(key, desc, false); // Step 4d if (!succeeded) { @@ -263,7 +263,7 @@ public final class NativeArray extends ScriptObject { } // not an index property - return super.defineOwnProperty(key, propertyDesc, reject); + return super.defineOwnProperty(key, desc, reject); } /** @@ -337,8 +337,9 @@ public final class NativeArray extends ScriptObject { */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object toString(final Object self) { - if (self instanceof ScriptObject) { - final ScriptObject sobj = (ScriptObject) self; + final Object obj = Global.toObject(self); + if (obj instanceof ScriptObject) { + final ScriptObject sobj = (ScriptObject)obj; try { final Object join = JOIN.getGetter().invokeExact(sobj); if (join instanceof ScriptFunction) { @@ -417,7 +418,7 @@ public final class NativeArray extends ScriptObject { long length; if (len instanceof Integer || len instanceof Long) { length = ((Number) len).longValue(); - if (length >= 0 && length < 0xffff_ffffL) { + if (length >= 0 && length < JSType.MAX_UINT) { return new NativeArray(length); } } @@ -573,9 +574,9 @@ public final class NativeArray extends ScriptObject { */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object join(final Object self, final Object separator) { - final String sep = separator == ScriptRuntime.UNDEFINED ? "," : JSType.toString(separator); final StringBuilder sb = new StringBuilder(); final Iterator<Object> iter = arrayLikeIterator(self, true); + final String sep = separator == ScriptRuntime.UNDEFINED ? "," : JSType.toString(separator); while (iter.hasNext()) { final Object obj = iter.next(); @@ -754,8 +755,9 @@ public final class NativeArray extends ScriptObject { final Object obj = Global.toObject(self); final ScriptObject sobj = (ScriptObject)obj; final long len = JSType.toUint32(sobj.getLength()); - final long relativeStartUint32 = JSType.toUint32(start); - final long relativeStart = JSType.toInteger(start); + final double startNum = JSType.toNumber(start); + final long relativeStartUint32 = JSType.toUint32(startNum); + final long relativeStart = JSType.toInteger(startNum); long k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : @@ -763,8 +765,9 @@ public final class NativeArray extends ScriptObject { Math.max(relativeStartUint32, relativeStart), len); - final long relativeEndUint32 = end == ScriptRuntime.UNDEFINED ? len : JSType.toUint32(end); - final long relativeEnd = end == ScriptRuntime.UNDEFINED ? len : JSType.toInteger(end); + final double endNum = (end == ScriptRuntime.UNDEFINED)? Double.NaN : JSType.toNumber(end); + final long relativeEndUint32 = (end == ScriptRuntime.UNDEFINED)? len : JSType.toUint32(endNum); + final long relativeEnd = (end == ScriptRuntime.UNDEFINED)? len : JSType.toInteger(endNum); final long finale = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : @@ -790,11 +793,15 @@ public final class NativeArray extends ScriptObject { } private static ScriptFunction compareFunction(final Object comparefn) { - try { - return (ScriptFunction)comparefn; - } catch (final ClassCastException e) { - return null; //undefined or null + if (comparefn == ScriptRuntime.UNDEFINED) { + return null; + } + + if (! (comparefn instanceof ScriptFunction)) { + throw typeError("not.a.function", ScriptRuntime.safeToString(comparefn)); } + + return (ScriptFunction)comparefn; } private static Object[] sort(final Object[] array, final Object comparefn) { @@ -816,7 +823,7 @@ public final class NativeArray extends ScriptObject { if (cmp != null) { try { - return (int)CALL_CMP.invokeExact(cmp, cmpThis, x, y); + return (int)Math.signum((double)CALL_CMP.invokeExact(cmp, cmpThis, x, y)); } catch (final RuntimeException | Error e) { throw e; } catch (final Throwable t) { @@ -846,17 +853,26 @@ public final class NativeArray extends ScriptObject { final long len = JSType.toUint32(sobj.getLength()); if (len > 1) { - final Object[] src = new Object[(int) len]; - for (int i = 0; i < src.length; i++) { - src[i] = sobj.get(i); + // Get only non-missing elements. Missing elements go at the end + // of the sorted array. So, just don't copy these to sort input. + + final ArrayList<Object> src = new ArrayList<>(); + for (int i = 0; i < (int)len; i++) { + if (sobj.has(i)) { + src.add(sobj.get(i)); + } } - final Object[] sorted = sort(src, comparefn); - assert sorted.length == src.length; + final Object[] sorted = sort(src.toArray(), comparefn); for (int i = 0; i < sorted.length; i++) { sobj.set(i, sorted[i], strict); } + + // delete missing elements - which are at the end of sorted array + for (int j = sorted.length; j < (int)len; j++) { + sobj.delete(j, strict); + } } return sobj; @@ -895,8 +911,9 @@ public final class NativeArray extends ScriptObject { final ScriptObject sobj = (ScriptObject)obj; final boolean strict = Global.isStrict(); final long len = JSType.toUint32(sobj.getLength()); - final long relativeStartUint32 = JSType.toUint32(start); - final long relativeStart = JSType.toInteger(start); + final double startNum = JSType.toNumber(start); + final long relativeStartUint32 = JSType.toUint32(startNum); + final long relativeStart = JSType.toInteger(startNum); //TODO: workaround overflow of relativeStart for start > Integer.MAX_VALUE final long actualStart = relativeStart < 0 ? diff --git a/src/jdk/nashorn/internal/objects/NativeDate.java b/src/jdk/nashorn/internal/objects/NativeDate.java index c4bf8e54..c97514a3 100644 --- a/src/jdk/nashorn/internal/objects/NativeDate.java +++ b/src/jdk/nashorn/internal/objects/NativeDate.java @@ -182,7 +182,8 @@ public final class NativeDate extends ScriptObject { @Override public String safeToString() { - return "[Date " + toISOStringImpl(this) + "]"; + final String str = isValidDate() ? toISOStringImpl(this) : INVALID_DATE; + return "[Date " + str + "]"; } @Override @@ -518,7 +519,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object getTimezoneOffset(final Object self) { final NativeDate nd = getNativeDate(self); - if (nd != null) { + if (nd != null && nd.isValidDate()) { final long msec = (long) nd.getTime(); return - nd.getTimeZone().getOffset(msec) / msPerMinute; } @@ -534,8 +535,8 @@ public final class NativeDate extends ScriptObject { */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object setTime(final Object self, final Object time) { - final double num = timeClip(JSType.toNumber(time)); final NativeDate nd = getNativeDate(self); + final double num = timeClip(JSType.toNumber(time)); nd.setTime(num); return num; } @@ -550,9 +551,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) public static Object setMilliseconds(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, MILLISECOND, args, true); - } + setFields(nd, MILLISECOND, args, true); return nd.getTime(); } @@ -566,9 +565,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) public static Object setUTCMilliseconds(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, MILLISECOND, args, false); - } + setFields(nd, MILLISECOND, args, false); return nd.getTime(); } @@ -582,9 +579,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2) public static Object setSeconds(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, SECOND, args, true); - } + setFields(nd, SECOND, args, true); return nd.getTime(); } @@ -598,9 +593,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2) public static Object setUTCSeconds(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, SECOND, args, false); - } + setFields(nd, SECOND, args, false); return nd.getTime(); } @@ -614,9 +607,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3) public static Object setMinutes(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, MINUTE, args, true); - } + setFields(nd, MINUTE, args, true); return nd.getTime(); } @@ -630,9 +621,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 3) public static Object setUTCMinutes(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, MINUTE, args, false); - } + setFields(nd, MINUTE, args, false); return nd.getTime(); } @@ -646,9 +635,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 4) public static Object setHours(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, HOUR, args, true); - } + setFields(nd, HOUR, args, true); return nd.getTime(); } @@ -662,9 +649,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 4) public static Object setUTCHours(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, HOUR, args, false); - } + setFields(nd, HOUR, args, false); return nd.getTime(); } @@ -678,9 +663,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) public static Object setDate(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, DAY, args, true); - } + setFields(nd, DAY, args, true); return nd.getTime(); } @@ -694,9 +677,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) public static Object setUTCDate(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, DAY, args, false); - } + setFields(nd, DAY, args, false); return nd.getTime(); } @@ -710,9 +691,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2) public static Object setMonth(final Object self, final Object... args) { final NativeDate nd = getNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, MONTH, args, true); - } + setFields(nd, MONTH, args, true); return nd.getTime(); } @@ -726,9 +705,7 @@ public final class NativeDate extends ScriptObject { @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2) public static Object setUTCMonth(final Object self, final Object... args) { final NativeDate nd = ensureNativeDate(self); - if (nd.isValidDate()) { - setFields(nd, MONTH, args, false); - } + setFields(nd, MONTH, args, false); return nd.getTime(); } @@ -746,7 +723,11 @@ public final class NativeDate extends ScriptObject { setFields(nd, YEAR, args, true); } else { final double[] d = convertArgs(args, 0, YEAR, YEAR, 3); - nd.setTime(timeClip(utc(makeDate(makeDay(d[0], d[1], d[2]), 0), nd.getTimeZone()))); + if (d != null) { + nd.setTime(timeClip(utc(makeDate(makeDay(d[0], d[1], d[2]), 0), nd.getTimeZone()))); + } else { + nd.setTime(NaN); + } } return nd.getTime(); } @@ -781,13 +762,13 @@ public final class NativeDate extends ScriptObject { public static Object setYear(final Object self, final Object year) { final NativeDate nd = getNativeDate(self); if (isNaN(nd.getTime())) { - return null; + nd.setTime(utc(0, nd.getTimeZone())); } final double yearNum = JSType.toNumber(year); if (isNaN(yearNum)) { nd.setTime(NaN); - return nd; + return nd.getTime(); } int yearInt = JSType.toInteger(yearNum); if (0 <= yearInt && yearInt <= 99) { @@ -795,7 +776,7 @@ public final class NativeDate extends ScriptObject { } setFields(nd, YEAR, new Object[] {yearInt}, true); - return nd; + return nd.getTime(); } /** @@ -844,10 +825,6 @@ public final class NativeDate extends ScriptObject { */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object toJSON(final Object self, final Object key) { - if (self instanceof NativeDate) { - final NativeDate nd = (NativeDate)self; - return (isNaN(nd.getTime())) ? null : toISOStringImpl(nd); - } // NOTE: Date.prototype.toJSON is generic. Accepts other objects as well. final Object selfObj = Global.toObject(self); if (!(selfObj instanceof ScriptObject)) { @@ -855,11 +832,11 @@ public final class NativeDate extends ScriptObject { } final ScriptObject sobj = (ScriptObject)selfObj; final Object value = sobj.getDefaultValue(Number.class); - - final double num = (value instanceof Number) ? ((Number)value).doubleValue() : NaN; - - if (isInfinite(num) || isNaN(num)) { - return null; + if (value instanceof Number) { + final double num = ((Number)value).doubleValue(); + if (isInfinite(num) || isNaN(num)) { + return null; + } } try { @@ -1200,13 +1177,18 @@ public final class NativeDate extends ScriptObject { // Convert Date constructor args, checking for NaN, filling in defaults etc. private static double[] convertCtorArgs(final Object[] args) { final double[] d = new double[7]; + boolean nullReturn = false; + // should not bailout on first NaN or infinite. Need to convert all + // subsequent args for possible side-effects via valueOf/toString overrides + // on argument objects. for (int i = 0; i < d.length; i++) { if (i < args.length) { final double darg = JSType.toNumber(args[i]); if (isNaN(darg) || isInfinite(darg)) { - return null; + nullReturn = true; } + d[i] = (long)darg; } else { d[i] = i == 2 ? 1 : 0; // day in month defaults to 1 @@ -1217,31 +1199,39 @@ public final class NativeDate extends ScriptObject { d[0] += 1900; } - return d; + return nullReturn? null : d; } // This method does the hard work for all setter methods: If a value is provided // as argument it is used, otherwise the value is calculated from the existing time value. private static double[] convertArgs(final Object[] args, final double time, final int fieldId, final int start, final int length) { final double[] d = new double[length]; + boolean nullReturn = false; + // Need to call toNumber on all args for side-effects - even if an argument + // fails to convert to number, subsequent toNumber calls needed for possible + // side-effects via valueOf/toString overrides. for (int i = start; i < start + length; i++) { if (fieldId <= i && i < fieldId + args.length) { final double darg = JSType.toNumber(args[i - fieldId]); if (isNaN(darg) || isInfinite(darg)) { - return null; + nullReturn = true; } + d[i - start] = (long) darg; } else { // Date.prototype.set* methods require first argument to be defined if (i == fieldId) { - return null; + nullReturn = true; + } + + if (! nullReturn) { + d[i - start] = valueFromTime(i, time); } - d[i - start] = valueFromTime(i, time); } } - return d; + return nullReturn? null : d; } // ECMA 15.9.1.14 TimeClip (time) @@ -1288,6 +1278,10 @@ public final class NativeDate extends ScriptObject { final double time = local ? nd.getLocalTime() : nd.getTime(); final double d[] = convertArgs(args, time, fieldId, start, length); + if (! nd.isValidDate()) { + return; + } + double newTime; if (d == null) { newTime = NaN; diff --git a/src/jdk/nashorn/internal/objects/NativeDebug.java b/src/jdk/nashorn/internal/objects/NativeDebug.java index b311981e..aba76c8e 100644 --- a/src/jdk/nashorn/internal/objects/NativeDebug.java +++ b/src/jdk/nashorn/internal/objects/NativeDebug.java @@ -247,7 +247,6 @@ public final class NativeDebug extends ScriptObject { out.println("Scope count " + ScriptObject.getScopeCount()); out.println("ScriptObject listeners added " + PropertyListenerManager.getListenersAdded()); out.println("ScriptObject listeners removed " + PropertyListenerManager.getListenersRemoved()); - out.println("ScriptObject listeners dead " + PropertyListenerManager.getListenersDead()); out.println("ScriptFunction count " + ScriptObject.getCount()); out.println("ScriptFunction invokes " + ScriptFunction.getInvokes()); out.println("ScriptFunction allocations " + ScriptFunction.getAllocations()); diff --git a/src/jdk/nashorn/internal/objects/NativeFunction.java b/src/jdk/nashorn/internal/objects/NativeFunction.java index f5c0c290..13c1bc15 100644 --- a/src/jdk/nashorn/internal/objects/NativeFunction.java +++ b/src/jdk/nashorn/internal/objects/NativeFunction.java @@ -81,23 +81,13 @@ public final class NativeFunction { Object[] args = null; - if (ScriptObject.isArray(array)) { - args = ((NativeArray)array).asObjectArray(); - } else if (array instanceof ScriptObject) { + if (array instanceof ScriptObject) { // look for array-like object final ScriptObject sobj = (ScriptObject)array; final Object len = sobj.getLength(); - - if (len == UNDEFINED || len == null) { - throw typeError("function.apply.expects.array"); - } - final int n = (int)JSType.toUint32(len); - if (n != JSType.toNumber(len)) { - throw typeError("function.apply.expects.array"); - } - args = new Object[(int)JSType.toUint32(len)]; + args = new Object[n]; for (int i = 0; i < args.length; i++) { args[i] = sobj.get(i); } diff --git a/src/jdk/nashorn/internal/objects/NativeJSAdapter.java b/src/jdk/nashorn/internal/objects/NativeJSAdapter.java index 409b9fa4..8a3f42d2 100644 --- a/src/jdk/nashorn/internal/objects/NativeJSAdapter.java +++ b/src/jdk/nashorn/internal/objects/NativeJSAdapter.java @@ -148,11 +148,7 @@ public final class NativeJSAdapter extends ScriptObject { if (overrides instanceof ScriptObject) { this.overrides = true; final ScriptObject sobj = (ScriptObject)overrides; - final Iterator<String> iter = sobj.propertyIterator(); - while (iter.hasNext()) { - final String prop = iter.next(); - super.set(prop, sobj.get(prop), false); - } + this.addBoundProperties(sobj); } else { this.overrides = false; } diff --git a/src/jdk/nashorn/internal/objects/NativeJSON.java b/src/jdk/nashorn/internal/objects/NativeJSON.java index faeebc1d..b3d6d873 100644 --- a/src/jdk/nashorn/internal/objects/NativeJSON.java +++ b/src/jdk/nashorn/internal/objects/NativeJSON.java @@ -229,7 +229,7 @@ public final class NativeJSON extends ScriptObject { final JSType type = JSType.of(value); if (type == JSType.OBJECT) { if (isArray(value)) { - return JA((NativeArray)value, state); + return JA((ScriptObject)value, state); } else if (value instanceof ScriptObject) { return JO((ScriptObject)value, state); } @@ -315,7 +315,7 @@ public final class NativeJSON extends ScriptObject { } // Spec: The abstract operation JA(value) serializes an array. - private static Object JA(final NativeArray value, final StringifyState state) { + private static Object JA(final ScriptObject value, final StringifyState state) { if (state.stack.containsKey(value)) { throw typeError("JSON.stringify.cyclic"); } diff --git a/src/jdk/nashorn/internal/objects/NativeJava.java b/src/jdk/nashorn/internal/objects/NativeJava.java index 2519c028..5faec5ea 100644 --- a/src/jdk/nashorn/internal/objects/NativeJava.java +++ b/src/jdk/nashorn/internal/objects/NativeJava.java @@ -394,22 +394,56 @@ public final class NativeJava { * </pre> * We can see several important concepts in the above example: * <ul> - * <li>Every Java class will have exactly one extender subclass in Nashorn - repeated invocations of {@code extend} - * for the same type will yield the same extender type. It's a generic adapter that delegates to whatever JavaScript - * functions its implementation object has on a per-instance basis.</li> + * <li>Every specified list of Java types will have exactly one extender subclass in Nashorn - repeated invocations + * of {@code extend} for the same list of types will yield the same extender type. It's a generic adapter that + * delegates to whatever JavaScript functions its implementation object has on a per-instance basis.</li> * <li>If the Java method is overloaded (as in the above example {@code List.add()}), then your JavaScript adapter * must be prepared to deal with all overloads.</li> * <li>You can't invoke {@code super.*()} from adapters for now.</li> + * <li>It is also possible to specify an ordinary JavaScript object as the last argument to {@code extend}. In that + * case, it is treated as a class-level override. {@code extend} will return an extender class where all instances + * will have the methods implemented by functions on that object, just as if that object were passed as the last + * argument to their constructor. Example: + * <pre> + * var Runnable = Java.type("java.lang.Runnable") + * var R1 = Java.extend(Runnable, { + * run: function() { + * print("R1.run() invoked!") + * } + * }) + * var r1 = new R1 + * var t = new java.lang.Thread(r1) + * t.start() + * t.join() + * </pre> + * As you can see, you don't have to pass any object when you create a new instance of {@code R1} as its + * {@code run()} function was defined already when extending the class. Of course, you can still provide + * instance-level overrides on these objects. The order of precedence is instance-level method, class-level method, + * superclass method, or {@code UnsupportedOperationException} if the superclass method is abstract. If we continue + * our previous example: + * <pre> + * var r2 = new R1(function() { print("r2.run() invoked!") }) + * r2.run() + * </pre> + * We'll see it'll print {@code "r2.run() invoked!"}, thus overriding on instance-level the class-level behavior. + * </li> * </ul> * @param self not used * @param types the original types. The caller must pass at least one Java type object of class {@link StaticClass} * representing either a public interface or a non-final public class with at least one public or protected * constructor. If more than one type is specified, at most one can be a class and the rest have to be interfaces. - * Invoking the method twice with exactly the same types in the same order will return the same adapter - * class, any reordering of types or even addition or removal of redundant types (i.e. interfaces that other types - * in the list already implement/extend, or {@code java.lang.Object} in a list of types consisting purely of - * interfaces) will result in a different adapter class, even though those adapter classes are functionally - * identical; we deliberately don't want to incur the additional processing cost of canonicalizing type lists. + * Invoking the method twice with exactly the same types in the same order - in absence of class-level overrides - + * will return the same adapter class, any reordering of types or even addition or removal of redundant types (i.e. + * interfaces that other types in the list already implement/extend, or {@code java.lang.Object} in a list of types + * consisting purely of interfaces) will result in a different adapter class, even though those adapter classes are + * functionally identical; we deliberately don't want to incur the additional processing cost of canonicalizing type + * lists. As a special case, the last argument can be a {@code ScriptObject} instead of a type. In this case, a + * separate adapter class is generated - new one for each invocation - that will use the passed script object as its + * implementation for all instances. Instances of such adapter classes can then be created without passing another + * script object in the constructor, as the class has a class-level behavior defined by the script object. However, + * you can still pass a script object (or if it's a SAM type, a function) to the constructor to provide further + * instance-level overrides. + * * @return a new {@link StaticClass} that represents the adapter for the original types. */ @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) @@ -417,14 +451,27 @@ public final class NativeJava { if(types == null || types.length == 0) { throw typeError("extend.expects.at.least.one.argument"); } - final Class<?>[] stypes = new Class<?>[types.length]; + final int l = types.length; + final int typesLen; + final ScriptObject classOverrides; + if(types[l - 1] instanceof ScriptObject) { + classOverrides = (ScriptObject)types[l - 1]; + typesLen = l - 1; + if(typesLen == 0) { + throw typeError("extend.expects.at.least.one.type.argument"); + } + } else { + classOverrides = null; + typesLen = l; + } + final Class<?>[] stypes = new Class<?>[typesLen]; try { - for(int i = 0; i < types.length; ++i) { + for(int i = 0; i < typesLen; ++i) { stypes[i] = ((StaticClass)types[i]).getRepresentedClass(); } } catch(final ClassCastException e) { throw typeError("extend.expects.java.types"); } - return JavaAdapterFactory.getAdapterClassFor(stypes); + return JavaAdapterFactory.getAdapterClassFor(stypes, classOverrides); } } diff --git a/src/jdk/nashorn/internal/objects/NativeRegExp.java b/src/jdk/nashorn/internal/objects/NativeRegExp.java index d386531f..9a12911f 100644 --- a/src/jdk/nashorn/internal/objects/NativeRegExp.java +++ b/src/jdk/nashorn/internal/objects/NativeRegExp.java @@ -523,20 +523,28 @@ public final class NativeRegExp extends ScriptObject { } private RegExpResult execInner(final String string) { + final boolean isGlobal = regexp.isGlobal(); + int start = getLastIndex(); + if (!isGlobal) { + start = 0; + } - final int start = regexp.isGlobal() ? getLastIndex() : 0; if (start < 0 || start > string.length()) { - setLastIndex(0); + if (isGlobal) { + setLastIndex(0); + } return null; } final RegExpMatcher matcher = regexp.match(string); if (matcher == null || !matcher.search(start)) { - setLastIndex(0); + if (isGlobal) { + setLastIndex(0); + } return null; } - if (regexp.isGlobal()) { + if (isGlobal) { setLastIndex(matcher.end()); } @@ -545,6 +553,22 @@ public final class NativeRegExp extends ScriptObject { return match; } + // String.prototype.split method ignores the global flag and should not update lastIndex property. + private RegExpResult execSplit(final String string, int start) { + if (start < 0 || start > string.length()) { + return null; + } + + final RegExpMatcher matcher = regexp.match(string); + if (matcher == null || !matcher.search(start)) { + return null; + } + + final RegExpResult match = new RegExpResult(string, matcher.start(), groups(matcher)); + globalObject.setLastRegExpResult(match); + return match; + } + /** * Convert java.util.regex.Matcher groups to JavaScript groups. * That is, replace null and groups that didn't match with undefined. @@ -597,7 +621,7 @@ public final class NativeRegExp extends ScriptObject { * @return True if a match is found. */ public Object test(final String string) { - return exec(string) != null; + return execInner(string) != null; } /** @@ -762,35 +786,31 @@ public final class NativeRegExp extends ScriptObject { * @return Array of substrings. */ Object split(final String string, final long limit) { - return split(this, string, limit); - } - - private static Object split(final NativeRegExp regexp0, final String input, final long limit) { - final List<Object> matches = new ArrayList<>(); - - final NativeRegExp regexp = new NativeRegExp(regexp0); - regexp.setGlobal(true); - if (limit == 0L) { return new NativeArray(); } + final List<Object> matches = new ArrayList<>(); + RegExpResult match; - final int inputLength = input.length(); + final int inputLength = string.length(); int lastLength = -1; + int lastIndex = 0; int lastLastIndex = 0; - while ((match = regexp.execInner(input)) != null) { - final int lastIndex = match.getIndex() + match.length(); + while ((match = execSplit(string, lastIndex)) != null) { + lastIndex = match.getIndex() + match.length(); if (lastIndex > lastLastIndex) { - matches.add(input.substring(lastLastIndex, match.getIndex())); - if (match.getGroups().length > 1 && match.getIndex() < inputLength) { - matches.addAll(Arrays.asList(match.getGroups()).subList(1, match.getGroups().length)); + matches.add(string.substring(lastLastIndex, match.getIndex())); + final Object[] groups = match.getGroups(); + if (groups.length > 1 && match.getIndex() < inputLength) { + for (int index = 1; index < groups.length && matches.size() < limit; index++) { + matches.add(groups[index]); + } } lastLength = match.length(); - lastLastIndex = lastIndex; if (matches.size() >= limit) { break; @@ -798,8 +818,10 @@ public final class NativeRegExp extends ScriptObject { } // bump the index to avoid infinite loop - if (regexp.getLastIndex() == match.getIndex()) { - regexp.setLastIndex(match.getIndex() + 1); + if (lastIndex == lastLastIndex) { + lastIndex++; + } else { + lastLastIndex = lastIndex; } } @@ -807,12 +829,12 @@ public final class NativeRegExp extends ScriptObject { // check special case if we need to append an empty string at the // end of the match // if the lastIndex was the entire string - if (lastLastIndex == input.length()) { - if (lastLength > 0 || regexp.test("") == Boolean.FALSE) { + if (lastLastIndex == string.length()) { + if (lastLength > 0 || execSplit("", 0) == null) { matches.add(""); } } else { - matches.add(input.substring(lastLastIndex, inputLength)); + matches.add(string.substring(lastLastIndex, inputLength)); } } diff --git a/src/jdk/nashorn/internal/objects/NativeRegExpExecResult.java b/src/jdk/nashorn/internal/objects/NativeRegExpExecResult.java index 5d4f8578..a88f69c7 100644 --- a/src/jdk/nashorn/internal/objects/NativeRegExpExecResult.java +++ b/src/jdk/nashorn/internal/objects/NativeRegExpExecResult.java @@ -51,6 +51,7 @@ public final class NativeRegExpExecResult extends ScriptObject { NativeRegExpExecResult(final RegExpResult result) { setProto(Global.instance().getArrayPrototype()); + setIsArray(); this.setArray(ArrayData.allocate(result.getGroups().clone())); this.index = result.getIndex(); this.input = result.getInput(); diff --git a/src/jdk/nashorn/internal/objects/NativeString.java b/src/jdk/nashorn/internal/objects/NativeString.java index 5f48ad98..2715d4f4 100644 --- a/src/jdk/nashorn/internal/objects/NativeString.java +++ b/src/jdk/nashorn/internal/objects/NativeString.java @@ -25,11 +25,11 @@ package jdk.nashorn.internal.objects; +import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndexNoThrow; -import static jdk.nashorn.internal.lookup.Lookup.MH; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -41,6 +41,7 @@ import java.util.List; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; +import jdk.nashorn.internal.lookup.MethodHandleFactory; import jdk.nashorn.internal.objects.annotations.Attribute; import jdk.nashorn.internal.objects.annotations.Constructor; import jdk.nashorn.internal.objects.annotations.Function; @@ -55,7 +56,6 @@ import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.arrays.ArrayIndex; -import jdk.nashorn.internal.lookup.MethodHandleFactory; import jdk.nashorn.internal.runtime.linker.NashornGuards; import jdk.nashorn.internal.runtime.linker.PrimitiveLookup; @@ -179,7 +179,6 @@ public final class NativeString extends ScriptObject { return ((ScriptObject) Global.toObject(self)).get(key); } - @SuppressWarnings("unused") private static Object get(final Object self, final int key) { final CharSequence cs = JSType.toCharSequence(self); if (key >= 0 && key < cs.length()) { @@ -838,15 +837,13 @@ public final class NativeString extends ScriptObject { */ @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object split(final Object self, final Object separator, final Object limit) { - final String str = checkObjectToString(self); + final long lim = (limit == UNDEFINED) ? JSType.MAX_UINT : JSType.toUint32(limit); if (separator == UNDEFINED) { - return new NativeArray(new Object[]{str}); + return lim == 0 ? new NativeArray() : new NativeArray(new Object[]{str}); } - final long lim = (limit == UNDEFINED) ? JSType.MAX_UINT : JSType.toUint32(limit); - if (separator instanceof NativeRegExp) { return ((NativeRegExp) separator).split(str, lim); } @@ -857,8 +854,9 @@ public final class NativeString extends ScriptObject { private static Object splitString(String str, String separator, long limit) { if (separator.isEmpty()) { - final Object[] array = new Object[str.length()]; - for (int i = 0; i < array.length; i++) { + final int length = (int) Math.min(str.length(), limit); + final Object[] array = new Object[length]; + for (int i = 0; i < length; i++) { array[i] = String.valueOf(str.charAt(i)); } return new NativeArray(array); diff --git a/src/jdk/nashorn/internal/objects/NativeUint32Array.java b/src/jdk/nashorn/internal/objects/NativeUint32Array.java index 7ad8a939..9be01312 100644 --- a/src/jdk/nashorn/internal/objects/NativeUint32Array.java +++ b/src/jdk/nashorn/internal/objects/NativeUint32Array.java @@ -29,6 +29,7 @@ import jdk.nashorn.internal.objects.annotations.Attribute; import jdk.nashorn.internal.objects.annotations.Constructor; import jdk.nashorn.internal.objects.annotations.Function; import jdk.nashorn.internal.objects.annotations.ScriptClass; +import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.arrays.ArrayData; @@ -71,17 +72,17 @@ public final class NativeUint32Array extends ArrayBufferView { @Override protected long getLongImpl(final int key) { - return getIntImpl(key) & 0xffff_ffffL; + return getIntImpl(key) & JSType.MAX_UINT; } @Override protected double getDoubleImpl(final int key) { - return getIntImpl(key) & 0xffff_ffffL; + return getIntImpl(key) & JSType.MAX_UINT; } @Override protected Object getObjectImpl(final int key) { - return getIntImpl(key) & 0xffff_ffffL; + return getIntImpl(key) & JSType.MAX_UINT; } @Override diff --git a/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java b/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java index 46b353f1..23a13f25 100644 --- a/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java +++ b/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java @@ -42,6 +42,10 @@ import jdk.nashorn.internal.lookup.Lookup; * function objects -- to expose properties like "prototype", "length" etc. */ public class ScriptFunctionImpl extends ScriptFunction { + + /** Reference to constructor prototype. */ + private Object prototype; + // property map for strict mode functions private static final PropertyMap strictmodemap$; // property map for bound functions @@ -49,6 +53,9 @@ public class ScriptFunctionImpl extends ScriptFunction { // property map for non-strict, non-bound functions. private static final PropertyMap nasgenmap$; + // Marker object for lazily initialized prototype object + private static final Object LAZY_PROTOTYPE = new Object(); + /** * Constructor called by Nasgen generated code, no membercount, use the default map. * Creates builtin functions only. @@ -83,8 +90,8 @@ public class ScriptFunctionImpl extends ScriptFunction { * @param methodHandle handle for invocation * @param scope scope object * @param specs specialized versions of this method, if available, null otherwise - * @param strict are we in strict mode - * @param builtin is this a built-in function + * @param isStrict are we in strict mode + * @param isBuiltin is this a built-in function * @param isConstructor can the function be used as a constructor (most can; some built-ins are restricted). */ ScriptFunctionImpl(final String name, final MethodHandle methodHandle, final ScriptObject scope, final MethodHandle[] specs, final boolean isStrict, final boolean isBuiltin, final boolean isConstructor) { @@ -125,11 +132,17 @@ public class ScriptFunctionImpl extends ScriptFunction { // function object representing TypeErrorThrower private static ScriptFunction typeErrorThrower; + /* + * ECMA section 13.2.3 The [[ThrowTypeError]] Function Object + */ static synchronized ScriptFunction getTypeErrorThrower() { if (typeErrorThrower == null) { - //name handle - final ScriptFunctionImpl func = new ScriptFunctionImpl("TypeErrorThrower", Lookup.TYPE_ERROR_THROWER_SETTER, null, null, false, false, false); + // use "getter" so that [[ThrowTypeError]] function's arity is 0 - as specified in step 10 of section 13.2.3 + final ScriptFunctionImpl func = new ScriptFunctionImpl("TypeErrorThrower", Lookup.TYPE_ERROR_THROWER_GETTER, null, null, false, false, false); func.setPrototype(UNDEFINED); + // Non-constructor built-in functions do not have "prototype" property + func.deleteOwnProperty(func.getMap().findProperty("prototype")); + func.preventExtensions(); typeErrorThrower = func; } @@ -152,7 +165,7 @@ public class ScriptFunctionImpl extends ScriptFunction { } private static PropertyMap createBoundFunctionMap(final PropertyMap strictModeMap) { - // Bond function map is same as strict function map, but additionally lacks the "prototype" property, see + // Bound function map is same as strict function map, but additionally lacks the "prototype" property, see // ECMAScript 5.1 section 15.3.4.5 return strictModeMap.deleteProperty(strictModeMap.findProperty("prototype")); } @@ -182,6 +195,8 @@ public class ScriptFunctionImpl extends ScriptFunction { static ScriptFunction makeFunction(final String name, final MethodHandle methodHandle, final MethodHandle[] specs) { final ScriptFunctionImpl func = new ScriptFunctionImpl(name, methodHandle, null, specs, false, true, false); func.setPrototype(UNDEFINED); + // Non-constructor built-in functions do not have "prototype" property + func.deleteOwnProperty(func.getMap().findProperty("prototype")); return func; } @@ -227,10 +242,23 @@ public class ScriptFunctionImpl extends ScriptFunction { return Global.objectPrototype(); } + @Override + public final Object getPrototype() { + if (prototype == LAZY_PROTOTYPE) { + prototype = new PrototypeObject(this); + } + return prototype; + } + + @Override + public final void setPrototype(final Object prototype) { + this.prototype = prototype; + } + // Internals below.. private void init() { this.setProto(Global.instance().getFunctionPrototype()); - this.setPrototype(new PrototypeObject(this)); + this.prototype = LAZY_PROTOTYPE; if (isStrict()) { final ScriptFunction func = getTypeErrorThrower(); diff --git a/src/jdk/nashorn/internal/parser/AbstractParser.java b/src/jdk/nashorn/internal/parser/AbstractParser.java index a3263b81..8f65e0f7 100644 --- a/src/jdk/nashorn/internal/parser/AbstractParser.java +++ b/src/jdk/nashorn/internal/parser/AbstractParser.java @@ -37,8 +37,8 @@ import jdk.nashorn.internal.runtime.ECMAErrors; import jdk.nashorn.internal.runtime.ErrorManager; import jdk.nashorn.internal.runtime.JSErrorType; import jdk.nashorn.internal.runtime.ParserException; -import jdk.nashorn.internal.runtime.regexp.RegExpFactory; import jdk.nashorn.internal.runtime.Source; +import jdk.nashorn.internal.runtime.regexp.RegExpFactory; /** * Base class for parsers. @@ -197,9 +197,10 @@ public abstract class AbstractParser { * * @param message Error message. * @param errorToken Offending token. + * @return ParserException upon failure. Caller should throw and not ignore */ - protected final void error(final String message, final long errorToken) { - error(JSErrorType.SYNTAX_ERROR, message, errorToken); + protected final ParserException error(final String message, final long errorToken) { + return error(JSErrorType.SYNTAX_ERROR, message, errorToken); } /** @@ -208,22 +209,24 @@ public abstract class AbstractParser { * @param errorType The error type * @param message Error message. * @param errorToken Offending token. + * @return ParserException upon failure. Caller should throw and not ignore */ - protected final void error(final JSErrorType errorType, final String message, final long errorToken) { + protected final ParserException error(final JSErrorType errorType, final String message, final long errorToken) { final int position = Token.descPosition(errorToken); final int lineNum = source.getLine(position); final int columnNum = source.getColumn(position); final String formatted = ErrorManager.format(message, source, lineNum, columnNum, errorToken); - throw new ParserException(errorType, formatted, source, lineNum, columnNum, errorToken); + return new ParserException(errorType, formatted, source, lineNum, columnNum, errorToken); } /** * Report an error. * * @param message Error message. + * @return ParserException upon failure. Caller should throw and not ignore */ - protected final void error(final String message) { - error(JSErrorType.SYNTAX_ERROR, message); + protected final ParserException error(final String message) { + return error(JSErrorType.SYNTAX_ERROR, message); } /** @@ -231,13 +234,24 @@ public abstract class AbstractParser { * * @param errorType The error type * @param message Error message. + * @return ParserException upon failure. Caller should throw and not ignore */ - protected final void error(final JSErrorType errorType, final String message) { + protected final ParserException error(final JSErrorType errorType, final String message) { // TODO - column needs to account for tabs. final int position = Token.descPosition(token); final int column = position - linePosition; final String formatted = ErrorManager.format(message, source, line, column, token); - throw new ParserException(errorType, formatted, source, line, column, token); + return new ParserException(errorType, formatted, source, line, column, token); + } + + /** + * Report a warning to the error manager. + * + * @param errorType The error type of the warning + * @param message Warning message. + */ + protected final void warning(final JSErrorType errorType, final String message, final long errorToken) { + errors.warning(error(errorType, message, errorToken)); } /** @@ -270,7 +284,7 @@ public abstract class AbstractParser { */ protected final void expect(final TokenType expected) throws ParserException { if (type != expected) { - error(expectMessage(expected)); + throw error(expectMessage(expected)); } next(); @@ -285,7 +299,7 @@ public abstract class AbstractParser { */ protected final Object expectValue(final TokenType expected) throws ParserException { if (type != expected) { - error(expectMessage(expected)); + throw error(expectMessage(expected)); } final Object value = getValue(); @@ -429,7 +443,7 @@ public abstract class AbstractParser { try { RegExpFactory.validate(regex.getExpression(), regex.getOptions()); } catch (final ParserException e) { - error(e.getMessage()); + throw error(e.getMessage()); } } node = LiteralNode.newInstance(source, literalToken, finish, (LexerToken)value); diff --git a/src/jdk/nashorn/internal/parser/JSONParser.java b/src/jdk/nashorn/internal/parser/JSONParser.java index 5468ca3d..e074cf01 100644 --- a/src/jdk/nashorn/internal/parser/JSONParser.java +++ b/src/jdk/nashorn/internal/parser/JSONParser.java @@ -170,8 +170,7 @@ public class JSONParser extends AbstractParser { } case '"': case '\\': - error(AbstractParser.message("unexpected.token", str)); - break; + throw error(AbstractParser.message("unexpected.token", str)); } } @@ -222,14 +221,12 @@ public class JSONParser extends AbstractParser { return new UnaryNode(source, literalToken, LiteralNode.newInstance(source, realToken, finish, (Number)value)); } - error(AbstractParser.message("expected", "number", type.getNameOrType())); - break; + throw error(AbstractParser.message("expected", "number", type.getNameOrType())); default: break; } - error(AbstractParser.message("expected", "json literal", type.getNameOrType())); - return null; + throw error(AbstractParser.message("expected", "json literal", type.getNameOrType())); } /** @@ -265,7 +262,7 @@ loop: elements.add(jsonLiteral()); // Comma between array elements is mandatory in JSON. if (type != COMMARIGHT && type != RBRACKET) { - error(AbstractParser.message("expected", ", or ]", type.getNameOrType())); + throw error(AbstractParser.message("expected", ", or ]", type.getNameOrType())); } break; } @@ -306,7 +303,7 @@ loop: // Comma between property assigments is mandatory in JSON. if (type != RBRACE && type != COMMARIGHT) { - error(AbstractParser.message("expected", ", or }", type.getNameOrType())); + throw error(AbstractParser.message("expected", ", or }", type.getNameOrType())); } break; } @@ -334,13 +331,11 @@ loop: if (name != null) { expect(COLON); final Node value = jsonLiteral(); - return new PropertyNode(source, propertyToken, value.getFinish(), name, value); + return new PropertyNode(source, propertyToken, value.getFinish(), name, value, null, null); } // Raise an error. - error(AbstractParser.message("expected", "string", type.getNameOrType())); - - return null; + throw error(AbstractParser.message("expected", "string", type.getNameOrType())); } } diff --git a/src/jdk/nashorn/internal/parser/Lexer.java b/src/jdk/nashorn/internal/parser/Lexer.java index 0c2255d4..d9ebb365 100644 --- a/src/jdk/nashorn/internal/parser/Lexer.java +++ b/src/jdk/nashorn/internal/parser/Lexer.java @@ -57,6 +57,9 @@ import jdk.nashorn.internal.runtime.options.Options; */ @SuppressWarnings("fallthrough") public class Lexer extends Scanner { + private static final long MIN_INT_L = Integer.MIN_VALUE; + private static final long MAX_INT_L = Integer.MAX_VALUE; + private static final boolean XML_LITERALS = Options.getBooleanProperty("nashorn.lexer.xmlliterals"); /** Content source. */ @@ -984,27 +987,27 @@ public class Lexer extends Scanner { */ private static Number valueOf(final String valueString, final int radix) throws NumberFormatException { try { - return Integer.valueOf(valueString, radix); + final long value = Long.parseLong(valueString, radix); + if(value >= MIN_INT_L && value <= MAX_INT_L) { + return Integer.valueOf((int)value); + } + return Long.valueOf(value); } catch (final NumberFormatException e) { - try { - return Long.valueOf(valueString, radix); - } catch (final NumberFormatException e2) { - if (radix == 10) { - return Double.valueOf(valueString); - } - - double value = 0.0; + if (radix == 10) { + return Double.valueOf(valueString); + } - for (int i = 0; i < valueString.length(); i++) { - final char ch = valueString.charAt(i); - // Preverified, should always be a valid digit. - final int digit = convertDigit(ch, radix); - value *= radix; - value += digit; - } + double value = 0.0; - return value; + for (int i = 0; i < valueString.length(); i++) { + final char ch = valueString.charAt(i); + // Preverified, should always be a valid digit. + final int digit = convertDigit(ch, radix); + value *= radix; + value += digit; } + + return value; } } diff --git a/src/jdk/nashorn/internal/parser/Parser.java b/src/jdk/nashorn/internal/parser/Parser.java index b810043e..ab70859b 100644 --- a/src/jdk/nashorn/internal/parser/Parser.java +++ b/src/jdk/nashorn/internal/parser/Parser.java @@ -54,24 +54,23 @@ import static jdk.nashorn.internal.parser.TokenType.TERNARY; import static jdk.nashorn.internal.parser.TokenType.WHILE; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Stack; import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.Namespace; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.BlockLexicalContext; import jdk.nashorn.internal.ir.BreakNode; import jdk.nashorn.internal.ir.BreakableNode; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; -import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.EmptyNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; @@ -84,6 +83,7 @@ import jdk.nashorn.internal.ir.LabelNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LineNumberNode; import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.LoopNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.ObjectNode; import jdk.nashorn.internal.ir.PropertyKey; @@ -117,9 +117,10 @@ public class Parser extends AbstractParser { /** Is scripting mode. */ private final boolean scripting; - private final LexicalContext lexicalContext = new LexicalContext(); private List<Node> functionDeclarations; + private final BlockLexicalContext lc = new BlockLexicalContext(); + /** Namespace for function names where not explicitly given */ private final Namespace namespace; @@ -146,7 +147,7 @@ public class Parser extends AbstractParser { */ public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict) { super(source, errors, strict); - this.env = env; + this.env = env; this.namespace = new Namespace(env.getNamespace()); this.scripting = env._scripting; } @@ -162,7 +163,7 @@ public class Parser extends AbstractParser { * @return function node resulting from successful parse */ public FunctionNode parse() { - return parse(RUN_SCRIPT.tag()); + return parse(RUN_SCRIPT.symbolName()); } /** @@ -176,7 +177,7 @@ public class Parser extends AbstractParser { */ public FunctionNode parse(final String scriptName) { final long t0 = Timing.isEnabled() ? System.currentTimeMillis() : 0L; - LOG.info(this + " begin for '" + scriptName + "'"); + LOG.info(this, " begin for '", scriptName, "'"); try { stream = new TokenStream(); @@ -214,7 +215,7 @@ public class Parser extends AbstractParser { final String end = this + " end '" + scriptName + "'"; if (Timing.isEnabled()) { Timing.accumulateTime(toString(), System.currentTimeMillis() - t0); - LOG.info(end + "' in " + (System.currentTimeMillis() - t0) + " ms"); + LOG.info(end, "' in ", (System.currentTimeMillis() - t0), " ms"); } else { LOG.info(end); } @@ -275,8 +276,7 @@ loop: */ private Block newBlock() { final Block block = new Block(source, token, Token.descPosition(token)); - lexicalContext.push(block); - return block; + return lc.push(block); } /** @@ -285,36 +285,60 @@ loop: * @param ident Name of function. * @return New block. */ - private FunctionNode newFunctionBlock(final IdentNode ident) { + private FunctionNode newFunctionNode(final long startToken, final IdentNode ident, final List<IdentNode> parameters, final FunctionNode.Kind kind) { // Build function name. final StringBuilder sb = new StringBuilder(); - final FunctionNode parentFunction = getFunction(); - if(parentFunction != null && !parentFunction.isProgram()) { + final FunctionNode parentFunction = lc.getCurrentFunction(); + if (parentFunction != null && !parentFunction.isProgram()) { sb.append(parentFunction.getName()).append('$'); } - sb.append(ident != null ? ident.getName() : FUNCTION_PREFIX.tag()); + sb.append(ident != null ? ident.getName() : FUNCTION_PREFIX.symbolName()); final String name = namespace.uniqueName(sb.toString()); - assert parentFunction != null || name.equals(RUN_SCRIPT.tag()) : "name = " + name;// must not rename runScript(). + assert parentFunction != null || name.equals(RUN_SCRIPT.symbolName()) : "name = " + name;// must not rename runScript(). - // Start new block. - final FunctionNode functionBlock = new FunctionNode(source, token, Token.descPosition(token), namespace, ident, name); - if(parentFunction == null) { - functionBlock.setProgram(); + int flags = 0; + if (parentFunction == null) { + flags |= FunctionNode.IS_PROGRAM; + } + if (isStrictMode) { + flags |= FunctionNode.IS_STRICT; } - functionBlock.setStrictMode(isStrictMode); - functionBlock.setState(errors.hasErrors() ? CompilationState.PARSE_ERROR : CompilationState.PARSED); - lexicalContext.push(functionBlock); - return functionBlock; + // Start new block. + FunctionNode functionNode = + new FunctionNode( + source, + token, + Token.descPosition(token), + startToken, + namespace, + ident, + name, + parameters, + kind, + flags); + + functionNode = functionNode.setState(lc, errors.hasErrors() ? CompilationState.PARSE_ERROR : CompilationState.PARSED); + lc.push(functionNode); + // Create new block, and just put it on the context stack, restoreFunctionNode() will associate it with the + // FunctionNode. + newBlock(); + return functionNode; } /** * Restore the current block. */ - private void restoreBlock(Block block) { - lexicalContext.pop(block); + private Block restoreBlock(final Block block) { + return lc.pop(block);//.setFlag(lc, flags); + } + + private FunctionNode restoreFunctionNode(final FunctionNode functionNode, final long lastToken) { + final Block newBody = restoreBlock(lc.getFunctionBody(functionNode)); + + return lc.pop(functionNode).setBody(lc, newBody).setLastToken(lc, lastToken); } /** @@ -323,23 +347,17 @@ loop: */ private Block getBlock(final boolean needsBraces) { // Set up new block. Captures LBRACE. - final Block newBlock = newBlock(); + Block newBlock = newBlock(); try { - pushControlNode(newBlock); - // Block opening brace. if (needsBraces) { expect(LBRACE); } + // Accumulate block statements. + statementList(); - try { - // Accumulate block statements. - statementList(); - } finally { - popControlNode(); - } } finally { - restoreBlock(newBlock); + newBlock = restoreBlock(newBlock); } final int possibleEnd = Token.descPosition(token) + Token.descLength(token); @@ -363,15 +381,12 @@ loop: return getBlock(true); } // Set up new block. Captures first token. - final Block newBlock = newBlock(); - + Block newBlock = newBlock(); try { - // Accumulate statements. statement(); } finally { - restoreBlock(newBlock); + newBlock = restoreBlock(newBlock); } - return newBlock; } @@ -382,11 +397,8 @@ loop: private void detectSpecialFunction(final IdentNode ident) { final String name = ident.getName(); - if (EVAL.tag().equals(name)) { - final Iterator<FunctionNode> it = lexicalContext.getFunctions(); - if(it.hasNext()) { - it.next().setHasEval(it); - } + if (EVAL.symbolName().equals(name)) { + markEval(lc); } } @@ -397,8 +409,8 @@ loop: private void detectSpecialProperty(final IdentNode ident) { final String name = ident.getName(); - if (ARGUMENTS.tag().equals(name)) { - getFunction().setUsesArguments(); + if (ARGUMENTS.symbolName().equals(name)) { + lc.setFlag(lc.getCurrentFunction(), FunctionNode.USES_ARGUMENTS); } } @@ -439,7 +451,7 @@ loop: lhs instanceof IndexNode || lhs instanceof IdentNode)) { if (env._early_lvalue_error) { - error(JSErrorType.REFERENCE_ERROR, AbstractParser.message("invalid.lvalue"), lhs.getToken()); + throw error(JSErrorType.REFERENCE_ERROR, AbstractParser.message("invalid.lvalue"), lhs.getToken()); } return referenceError(lhs, rhs); } @@ -469,143 +481,13 @@ loop: * @return Reduced expression. */ private Node incDecExpression(final long firstToken, final TokenType tokenType, final Node expression, final boolean isPostfix) { - long incDecToken = firstToken; - if (isPostfix) { - incDecToken = Token.recast(incDecToken, tokenType == DECPREFIX ? DECPOSTFIX : INCPOSTFIX); - } - - final UnaryNode node = new UnaryNode(source, incDecToken, expression); if (isPostfix) { - node.setStart(expression.getStart()); - node.setFinish(Token.descPosition(incDecToken) + Token.descLength(incDecToken)); - } - - return node; - } - - /** - * Find a label node in the label stack. - * @param ident Ident to find. - * @return null or the found label node. - */ - private LabelNode findLabel(final IdentNode ident) { - for (final LabelNode labelNode : getFunction().getLabelStack()) { - if (labelNode.getLabel().equals(ident)) { - return labelNode; - } - } - - return null; - } - - /** - * Add a label to the label stack. - * @param labelNode Label to add. - */ - private void pushLabel(final LabelNode labelNode) { - getFunction().getLabelStack().push(labelNode); - } - - /** - * Remove a label from the label stack. - */ - private void popLabel() { - getFunction().getLabelStack().pop(); - } - - /** - * Track the current nesting of controls for break and continue. - * @param node For, while, do or switch node. - */ - private void pushControlNode(final Node node) { - final boolean isLoop = node instanceof WhileNode; - final boolean isBreakable = node instanceof BreakableNode || node instanceof Block; - final FunctionNode function = getFunction(); - function.getControlStack().push(node); - - for (final LabelNode labelNode : function.getLabelStack()) { - if (isBreakable && labelNode.getBreakNode() == null) { - labelNode.setBreakNode(node); - } - - if (isLoop && labelNode.getContinueNode() == null) { - labelNode.setContinueNode(node); - } - } - } - - /** - * Finish with control. - */ - private void popControlNode() { - // Get control stack. - final Stack<Node> controlStack = getFunction().getControlStack(); - - // Can be empty if missing brace. - if (!controlStack.isEmpty()) { - controlStack.pop(); - } - } - - private void popControlNode(final Node node) { - // Get control stack. - final Stack<Node> controlStack = getFunction().getControlStack(); - - // Can be empty if missing brace. - if (!controlStack.isEmpty() && controlStack.peek() == node) { - controlStack.pop(); - } - } - - private boolean isInWithBlock() { - final Stack<Node> controlStack = getFunction().getControlStack(); - for (int i = controlStack.size() - 1; i >= 0; i--) { - final Node node = controlStack.get(i); - - if (node instanceof WithNode) { - return true; - } - } - - return false; - } - - private <T extends Node> T findControl(final Class<T> ctype) { - final Stack<Node> controlStack = getFunction().getControlStack(); - for (int i = controlStack.size() - 1; i >= 0; i--) { - final Node node = controlStack.get(i); - - if (ctype.isAssignableFrom(node.getClass())) { - return ctype.cast(node); - } - } - - return null; - } - - private <T extends Node> List<T> findControls(final Class<T> ctype, final Node to) { - final List<T> nodes = new ArrayList<>(); - final Stack<Node> controlStack = getFunction().getControlStack(); - for (int i = controlStack.size() - 1; i >= 0; i--) { - final Node node = controlStack.get(i); - - if (to == node) { - break; //stop looking - } - - if (ctype.isAssignableFrom(node.getClass())) { - nodes.add(ctype.cast(node)); - } + return new UnaryNode(source, Token.recast(firstToken, tokenType == DECPREFIX ? DECPOSTFIX : INCPOSTFIX), expression.getStart(), Token.descPosition(firstToken) + Token.descLength(firstToken), expression); } - return nodes; + return new UnaryNode(source, firstToken, expression); } - private <T extends Node> int countControls(final Class<T> ctype, final Node to) { - return findControls(ctype, to).size(); - } - - /** * ----------------------------------------------------------------------- * @@ -630,18 +512,23 @@ loop: final long functionToken = Token.toDesc(FUNCTION, 0, source.getLength()); // Set up the script to append elements. - final FunctionNode script = newFunctionBlock(new IdentNode(source, functionToken, Token.descPosition(functionToken), scriptName)); + FunctionNode script = newFunctionNode( + functionToken, + new IdentNode(source, functionToken, Token.descPosition(functionToken), scriptName), + new ArrayList<IdentNode>(), + FunctionNode.Kind.SCRIPT); - script.setKind(FunctionNode.Kind.SCRIPT); - script.setFirstToken(functionToken); functionDeclarations = new ArrayList<>(); sourceElements(); - script.prependStatements(functionDeclarations); + addFunctionDeclarations(script); functionDeclarations = null; + expect(EOF); - script.setLastToken(token); + script.setFinish(source.getLength() - 1); + script = restoreFunctionNode(script, token); //commit code + script = script.setBody(lc, script.getBody().setNeedsScope(lc)); return script; } @@ -670,24 +557,6 @@ loop: } /** - * Return last node in a statement list. - * - * @param statements Statement list. - * - * @return Last (non-debug) statement or null if empty block. - */ - private static Node lastStatement(final List<Node> statements) { - for (int lastIndex = statements.size() - 1; lastIndex >= 0; lastIndex--) { - final Node node = statements.get(lastIndex); - if (!node.isDebug()) { - return node; - } - } - - return null; - } - - /** * SourceElements : * SourceElement * SourceElements SourceElement @@ -716,7 +585,7 @@ loop: // check for directive prologues if (checkDirective) { // skip any debug statement like line number to get actual first line - final Node lastStatement = lastStatement(getBlock().getStatements()); + final Node lastStatement = lc.getLastStatement(); // get directive prologue, if any final String directive = getDirective(lastStatement); @@ -736,8 +605,8 @@ loop: // handle use strict directive if ("use strict".equals(directive)) { isStrictMode = true; - final FunctionNode function = getFunction(); - function.setStrictMode(true); + final FunctionNode function = lc.getCurrentFunction(); + lc.setFlag(lc.getCurrentFunction(), FunctionNode.IS_STRICT); // We don't need to check these, if lexical environment is already strict if (!oldStrictMode && directiveStmts != null) { @@ -759,7 +628,7 @@ loop: } } } catch (final Exception e) { - // Recover parsing. + //recover parsing recover(e); } @@ -806,14 +675,13 @@ loop: if (type == FUNCTION) { // As per spec (ECMA section 12), function declarations as arbitrary statement // is not "portable". Implementation can issue a warning or disallow the same. - if (isStrictMode && !topLevel) { - error(AbstractParser.message("strict.no.func.here"), token); - } functionExpression(true, topLevel); return; } - getBlock().addStatement(lineNumberNode); + if (lineNumberNode != null) { + appendStatement(lineNumberNode); + } switch (type) { case LBRACE: @@ -893,13 +761,9 @@ loop: * Parse a statement block. */ private void block() { - // Get statements in block. final Block newBlock = getBlock(true); - // Force block execution. - final ExecuteNode executeNode = new ExecuteNode(source, newBlock.getToken(), finish, newBlock); - - getBlock().addStatement(executeNode); + appendStatement(new ExecuteNode(source, newBlock.getToken(), finish, newBlock)); } /** @@ -942,7 +806,7 @@ loop: switch (ident.getName()) { case "eval": case "arguments": - error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken()); + throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken()); default: break; } @@ -995,8 +859,7 @@ loop: // Allocate var node. final VarNode var = new VarNode(source, varToken, finish, name, init); vars.add(var); - // Add to current block. - getBlock().addStatement(var); + appendStatement(var); if (type != COMMARIGHT) { break; @@ -1009,7 +872,7 @@ loop: boolean semicolon = type == SEMICOLON; endOfLine(); if (semicolon) { - getBlock().setFinish(finish); + lc.getCurrentBlock().setFinish(finish); } } @@ -1026,8 +889,7 @@ loop: */ private void emptyStatement() { if (env._empty_statements) { - getBlock().addStatement(new EmptyNode(source, token, - Token.descPosition(token) + Token.descLength(token))); + appendStatement(new EmptyNode(source, token, Token.descPosition(token) + Token.descLength(token))); } // SEMICOLON checked in caller. @@ -1052,7 +914,7 @@ loop: ExecuteNode executeNode = null; if (expression != null) { executeNode = new ExecuteNode(source, expressionToken, finish, expression); - getBlock().addStatement(executeNode); + appendStatement(executeNode); } else { expect(null); } @@ -1061,7 +923,7 @@ loop: if (executeNode != null) { executeNode.setFinish(finish); - getBlock().setFinish(finish); + lc.getCurrentBlock().setFinish(finish); } } @@ -1081,29 +943,17 @@ loop: next(); expect(LPAREN); - - // Get the test expression. final Node test = expression(); - expect(RPAREN); - - // Get the pass statement. final Block pass = getStatement(); - // Assume no else. Block fail = null; - if (type == ELSE) { next(); - - // Get the else block. fail = getStatement(); } - // Construct and add new if node. - final IfNode ifNode = new IfNode(source, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail); - - getBlock().addStatement(ifNode); + appendStatement(new IfNode(source, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail)); } /** @@ -1120,12 +970,12 @@ loop: */ private void forStatement() { // Create FOR node, capturing FOR token. - final ForNode forNode = new ForNode(source, token, Token.descPosition(token)); + ForNode forNode = new ForNode(source, token, Token.descPosition(token), null, null, null, null, ForNode.IS_FOR); - pushControlNode(forNode); // Set up new block for scope of vars. Captures first token. - final Block outer = newBlock(); + Block outer = newBlock(); + lc.push(forNode); try { // FOR tested in caller. @@ -1134,31 +984,97 @@ loop: // Nashorn extension: for each expression. // iterate property values rather than property names. if (!env._no_syntax_extensions && type == IDENT && "each".equals(getValue())) { - forNode.setIsForEach(); + forNode = forNode.setIsForEach(lc); next(); } expect(LPAREN); - /// Capture control information. - forControl(forNode); + List<VarNode> vars = null; + + switch (type) { + case VAR: + // Var statements captured in for outer block. + vars = variableStatement(false); + break; + case SEMICOLON: + break; + default: + final Node expression = expression(unaryExpression(), COMMARIGHT.getPrecedence(), true); + forNode = forNode.setInit(lc, expression); + break; + } + + switch (type) { + case SEMICOLON: + // for (init; test; modify) + expect(SEMICOLON); + if (type != SEMICOLON) { + forNode = forNode.setTest(lc, expression()); + } + expect(SEMICOLON); + if (type != RPAREN) { + forNode = forNode.setModify(lc, expression()); + } + break; + + case IN: + forNode = forNode.setIsForIn(lc); + if (vars != null) { + // for (var i in obj) + if (vars.size() == 1) { + forNode = forNode.setInit(lc, new IdentNode(vars.get(0).getName())); + } else { + // for (var i, j in obj) is invalid + throw error(AbstractParser.message("many.vars.in.for.in.loop"), vars.get(1).getToken()); + } + + } else { + // for (expr in obj) + final Node init = forNode.getInit(); + assert init != null : "for..in init expression can not be null here"; + + // check if initial expression is a valid L-value + if (!(init instanceof AccessNode || + init instanceof IndexNode || + init instanceof IdentNode)) { + throw error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken()); + } + + if (init instanceof IdentNode) { + if (!checkIdentLValue((IdentNode)init)) { + throw error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken()); + } + verifyStrictIdent((IdentNode)init, "for-in iterator"); + } + } + + next(); + + // Get the collection expression. + forNode = forNode.setModify(lc, expression()); + break; + + default: + expect(SEMICOLON); + break; + } expect(RPAREN); // Set the for body. final Block body = getStatement(); - forNode.setBody(body); + forNode = forNode.setBody(lc, body); forNode.setFinish(body.getFinish()); outer.setFinish(body.getFinish()); - // Add for to current block. - getBlock().addStatement(forNode); + appendStatement(forNode); } finally { - restoreBlock(outer); - popControlNode(); + lc.pop(forNode); + outer = restoreBlock(outer); } - getBlock().addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer)); + appendStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer)); } /** @@ -1175,87 +1091,7 @@ loop: * comprehensions. * @param forNode Owning FOR. */ - private void forControl(final ForNode forNode) { - List<VarNode> vars = null; - - switch (type) { - case VAR: - // Var statements captured in for outer block. - vars = variableStatement(false); - break; - - case SEMICOLON: - break; - - default: - final Node expression = expression(unaryExpression(), COMMARIGHT.getPrecedence(), true); - forNode.setInit(expression); - } - - switch (type) { - case SEMICOLON: - // for (init; test; modify) - expect(SEMICOLON); - - // Get the test expression. - if (type != SEMICOLON) { - forNode.setTest(expression()); - } - - expect(SEMICOLON); - - // Get the modify expression. - if (type != RPAREN) { - final Node expression = expression(); - forNode.setModify(expression); - } - - break; - - case IN: - forNode.setIsForIn(); - if (vars != null) { - // for (var i in obj) - if (vars.size() == 1) { - forNode.setInit(new IdentNode(vars.get(0).getName())); - } else { - // for (var i, j in obj) is invalid - error(AbstractParser.message("many.vars.in.for.in.loop"), vars.get(1).getToken()); - } - - } else { - // for (expr in obj) - final Node init = forNode.getInit(); - assert init != null : "for..in init expression can not be null here"; - - // check if initial expression is a valid L-value - if (!(init instanceof AccessNode || - init instanceof IndexNode || - init instanceof IdentNode)) { - error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken()); - } - - if (init instanceof IdentNode) { - if (!checkIdentLValue((IdentNode)init)) { - error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken()); - } - verifyStrictIdent((IdentNode)init, "for-in iterator"); - } - } - - next(); - - // Get the collection expression. - forNode.setModify(expression()); - break; - - default: - expect(SEMICOLON); - break; - } - - } /** * ...IterationStatement : @@ -1274,27 +1110,17 @@ loop: next(); // Construct WHILE node. - final WhileNode whileNode = new WhileNode(source, whileToken, Token.descPosition(whileToken)); - pushControlNode(whileNode); + WhileNode whileNode = new WhileNode(source, whileToken, Token.descPosition(whileToken), false); + lc.push(whileNode); try { expect(LPAREN); - - // Get the test expression. - final Node test = expression(); - whileNode.setTest(test); - + whileNode = whileNode.setTest(lc, expression()); expect(RPAREN); - - // Get WHILE body. - final Block statements = getStatement(); - whileNode.setBody(statements); - whileNode.setFinish(statements.getFinish()); - - // Add WHILE node. - getBlock().addStatement(whileNode); + whileNode = whileNode.setBody(lc, getStatement()); + appendStatement(whileNode); } finally { - popControlNode(); + lc.pop(whileNode); } } @@ -1314,34 +1140,25 @@ loop: // DO tested in the caller. next(); - final WhileNode doWhileNode = new DoWhileNode(source, doToken, Token.descPosition(doToken)); - pushControlNode(doWhileNode); + WhileNode doWhileNode = new WhileNode(source, doToken, Token.descPosition(doToken), true); + lc.push(doWhileNode); try { // Get DO body. - final Block statements = getStatement(); - doWhileNode.setBody(statements); + doWhileNode = doWhileNode.setBody(lc, getStatement()); expect(WHILE); - expect(LPAREN); - - // Get the test expression. - final Node test = expression(); - doWhileNode.setTest(test); - + doWhileNode = doWhileNode.setTest(lc, expression()); expect(RPAREN); if (type == SEMICOLON) { endOfLine(); } - doWhileNode.setFinish(finish); - - // Add DO node. - getBlock().addStatement(doWhileNode); + appendStatement(doWhileNode); } finally { - popControlNode(); + lc.pop(doWhileNode); } } @@ -1370,28 +1187,26 @@ loop: default: final IdentNode ident = getIdent(); - labelNode = findLabel(ident); + labelNode = lc.findLabel(ident.getName()); if (labelNode == null) { - error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken()); + throw error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken()); } break; } - final Node targetNode = labelNode != null ? labelNode.getContinueNode() : findControl(WhileNode.class); + final IdentNode label = labelNode == null ? null : labelNode.getLabel(); + final LoopNode targetNode = lc.getContinueTo(label); if (targetNode == null) { - error(AbstractParser.message("illegal.continue.stmt"), continueToken); + throw error(AbstractParser.message("illegal.continue.stmt"), continueToken); } endOfLine(); // Construct and add CONTINUE node. - final ContinueNode continueNode = new ContinueNode(source, continueToken, finish, labelNode, targetNode, findControl(TryNode.class)); - continueNode.setScopeNestingLevel(countControls(WithNode.class, targetNode)); - - getBlock().addStatement(continueNode); + appendStatement(new ContinueNode(source, continueToken, finish, label == null ? null : new IdentNode(label))); } /** @@ -1418,28 +1233,27 @@ loop: default: final IdentNode ident = getIdent(); - labelNode = findLabel(ident); + labelNode = lc.findLabel(ident.getName()); if (labelNode == null) { - error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken()); + throw error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken()); } break; } - final Node targetNode = labelNode != null ? labelNode.getBreakNode() : findControl(BreakableNode.class); - + //either an explicit label - then get its node or just a "break" - get first breakable + //targetNode is what we are breaking out from. + final IdentNode label = labelNode == null ? null : labelNode.getLabel(); + final BreakableNode targetNode = lc.getBreakable(label); if (targetNode == null) { - error(AbstractParser.message("illegal.break.stmt"), breakToken); + throw error(AbstractParser.message("illegal.break.stmt"), breakToken); } endOfLine(); // Construct and add BREAK node. - final BreakNode breakNode = new BreakNode(source, breakToken, finish, labelNode, targetNode, findControl(TryNode.class)); - breakNode.setScopeNestingLevel(countControls(WithNode.class, targetNode)); - - getBlock().addStatement(breakNode); + appendStatement(new BreakNode(source, breakToken, finish, label == null ? null : new IdentNode(label))); } /** @@ -1452,8 +1266,8 @@ loop: */ private void returnStatement() { // check for return outside function - if (getFunction().getKind() == FunctionNode.Kind.SCRIPT) { - error(AbstractParser.message("invalid.return")); + if (lc.getCurrentFunction().getKind() == FunctionNode.Kind.SCRIPT) { + throw error(AbstractParser.message("invalid.return")); } // Capture RETURN token. @@ -1478,8 +1292,7 @@ loop: endOfLine(); // Construct and add RETURN node. - final ReturnNode returnNode = new ReturnNode(source, returnToken, finish, expression, findControl(TryNode.class)); - getBlock().addStatement(returnNode); + appendStatement(new ReturnNode(source, returnToken, finish, expression)); } /** @@ -1513,8 +1326,7 @@ loop: endOfLine(); // Construct and add YIELD node. - final ReturnNode yieldNode = new ReturnNode(source, yieldToken, finish, expression, findControl(TryNode.class)); - getBlock().addStatement(yieldNode); + appendStatement(new ReturnNode(source, yieldToken, finish, expression)); } /** @@ -1533,35 +1345,23 @@ loop: // ECMA 12.10.1 strict mode restrictions if (isStrictMode) { - error(AbstractParser.message("strict.no.with"), withToken); + throw error(AbstractParser.message("strict.no.with"), withToken); } // Get WITH expression. - final WithNode withNode = new WithNode(source, withToken, finish, null, null); - final Iterator<FunctionNode> it = lexicalContext.getFunctions(); - if(it.hasNext()) { - it.next().setHasWith(it); - } + WithNode withNode = new WithNode(source, withToken, finish); try { - pushControlNode(withNode); - + lc.push(withNode); expect(LPAREN); - - final Node expression = expression(); - withNode.setExpression(expression); - + withNode = withNode.setExpression(lc, expression()); expect(RPAREN); - - // Get WITH body. - final Block statements = getStatement(); - withNode.setBody(statements); - withNode.setFinish(finish); + withNode = withNode.setBody(lc, getStatement()); } finally { - popControlNode(withNode); + lc.pop(withNode); } - getBlock().addStatement(withNode); + appendStatement(withNode); } /** @@ -1587,22 +1387,17 @@ loop: * Parse SWITCH statement. */ private void switchStatement() { - // Capture SWITCH token. final long switchToken = token; // SWITCH tested in caller. next(); // Create and add switch statement. - final SwitchNode switchNode = new SwitchNode(source, switchToken, Token.descPosition(switchToken)); - pushControlNode(switchNode); + SwitchNode switchNode = new SwitchNode(source, switchToken, Token.descPosition(switchToken), null, new ArrayList<CaseNode>(), null); + lc.push(switchNode); try { expect(LPAREN); - - // Get switch expression. - final Node switchExpression = expression(); - switchNode.setExpression(switchExpression); - + switchNode = switchNode.setExpression(lc, expression()); expect(RPAREN); expect(LBRACE); @@ -1619,19 +1414,14 @@ loop: switch (type) { case CASE: next(); - - // Get case expression. caseExpression = expression(); - break; case DEFAULT: if (defaultCase != null) { - error(AbstractParser.message("duplicate.default.in.switch")); + throw error(AbstractParser.message("duplicate.default.in.switch")); } - next(); - break; default: @@ -1654,16 +1444,13 @@ loop: cases.add(caseNode); } - switchNode.setCases(cases); - switchNode.setDefaultCase(defaultCase); - + switchNode = switchNode.setCases(lc, cases, defaultCase); next(); - switchNode.setFinish(finish); - getBlock().addStatement(switchNode); + appendStatement(switchNode); } finally { - popControlNode(); + lc.pop(switchNode); } } @@ -1683,23 +1470,19 @@ loop: expect(COLON); - if (findLabel(ident) != null) { - error(AbstractParser.message("duplicate.label", ident.getName()), labelToken); + if (lc.findLabel(ident.getName()) != null) { + throw error(AbstractParser.message("duplicate.label", ident.getName()), labelToken); } + LabelNode labelNode = new LabelNode(source, labelToken, finish, ident, null); try { - // Create and add label. - final LabelNode labelNode = new LabelNode(source, labelToken, finish, ident, null); - pushLabel(labelNode); - // Get and save body. - final Block statements = getStatement(); - labelNode.setBody(statements); + lc.push(labelNode); + labelNode = labelNode.setBody(lc, getStatement()); labelNode.setFinish(finish); - - getBlock().addStatement(labelNode); + appendStatement(labelNode); } finally { - // Remove label. - popLabel(); + assert lc.peek() instanceof LabelNode; + lc.pop(labelNode); } } @@ -1732,14 +1515,12 @@ loop: } if (expression == null) { - error(AbstractParser.message("expected.operand", type.getNameOrType())); + throw error(AbstractParser.message("expected.operand", type.getNameOrType())); } endOfLine(); - // Construct and add THROW node. - final ThrowNode throwNode = new ThrowNode(source, throwToken, finish, expression, findControl(TryNode.class)); - getBlock().addStatement(throwNode); + appendStatement(new ThrowNode(source, throwToken, finish, expression)); } /** @@ -1766,28 +1547,18 @@ loop: next(); // Container block needed to act as target for labeled break statements - final Block outer = newBlock(); - pushControlNode(outer); + Block outer = newBlock(); // Create try. - final TryNode tryNode = new TryNode(source, tryToken, Token.descPosition(tryToken), findControl(TryNode.class)); - pushControlNode(tryNode); try { - // Get TRY body. - final Block tryBody = getBlock(true); - - // Prepare to accumulate catches. + final Block tryBody = getBlock(true); final List<Block> catchBlocks = new ArrayList<>(); while (type == CATCH) { - // Capture CATCH token. final long catchToken = token; next(); - expect(LPAREN); - - // Get exception ident. final IdentNode exception = getIdent(); // ECMA 12.4.1 strict mode restrictions @@ -1795,28 +1566,23 @@ loop: // Check for conditional catch. Node ifExpression = null; - if (type == IF) { next(); - // Get the exception condition. ifExpression = expression(); } expect(RPAREN); - final Block catchBlock = newBlock(); + Block catchBlock = newBlock(); try { - // Get CATCH body. final Block catchBody = getBlock(true); - - // Create and add catch. final CatchNode catchNode = new CatchNode(source, catchToken, finish, exception, ifExpression, catchBody); - getBlock().addStatement(catchNode); - catchBlocks.add(catchBlock); + appendStatement(catchNode); } finally { - restoreBlock(catchBlock); + catchBlock = restoreBlock(catchBlock); + catchBlocks.add(catchBlock); } // If unconditional catch then should to be the end. @@ -1825,38 +1591,32 @@ loop: } } - popControlNode(); - // Prepare to capture finally statement. Block finallyStatements = null; if (type == FINALLY) { next(); - - // Get FINALLY body. finallyStatements = getBlock(true); } // Need at least one catch or a finally. if (catchBlocks.isEmpty() && finallyStatements == null) { - error(AbstractParser.message("missing.catch.or.finally"), tryToken); + throw error(AbstractParser.message("missing.catch.or.finally"), tryToken); } - tryNode.setBody(tryBody); - tryNode.setCatchBlocks(catchBlocks); - tryNode.setFinallyBody(finallyStatements); + final TryNode tryNode = new TryNode(source, tryToken, Token.descPosition(tryToken), tryBody, catchBlocks, finallyStatements); + // Add try. + assert lc.peek() == outer; + appendStatement(tryNode); + tryNode.setFinish(finish); outer.setFinish(finish); - // Add try. - outer.addStatement(tryNode); } finally { - popControlNode(tryNode); - restoreBlock(outer); - popControlNode(outer); + outer = restoreBlock(outer); } - getBlock().addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer)); + appendStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer)); } /** @@ -1872,11 +1632,8 @@ loop: final long debuggerToken = token; // DEBUGGER tested in caller. next(); - endOfLine(); - - final RuntimeNode runtimeNode = new RuntimeNode(source, debuggerToken, finish, RuntimeNode.Request.DEBUGGER, new ArrayList<Node>()); - getBlock().addStatement(runtimeNode); + appendStatement(new RuntimeNode(source, debuggerToken, finish, RuntimeNode.Request.DEBUGGER, new ArrayList<Node>())); } /** @@ -1912,7 +1669,7 @@ loop: return ident; case OCTAL: if (isStrictMode) { - error(AbstractParser.message("strict.no.octal"), token); + throw error(AbstractParser.message("strict.no.octal"), token); } case STRING: case ESCSTRING: @@ -2036,7 +1793,7 @@ loop: default: if (!elision) { - error(AbstractParser.message("expected.comma", type.getNameOrType())); + throw error(AbstractParser.message("expected.comma", type.getNameOrType())); } // Add expression element. final Node expression = assignmentExpression(false); @@ -2077,8 +1834,8 @@ loop: // Object context. // Prepare to accumulate elements. - final List<Node> elements = new ArrayList<>(); - final Map<Object, PropertyNode> map = new HashMap<>(); + // final List<Node> elements = new ArrayList<>(); + final Map<String, PropertyNode> map = new LinkedHashMap<>(); // Create a block for the object literal. boolean commaSeen = true; @@ -2096,25 +1853,30 @@ loop: default: if (!commaSeen) { - error(AbstractParser.message("expected.comma", type.getNameOrType())); - } + throw error(AbstractParser.message("expected.comma", type.getNameOrType())); + } - commaSeen = false; - // Get and add the next property. - final PropertyNode property = propertyAssignment(); - final Object key = property.getKeyName(); - final PropertyNode existingProperty = map.get(key); + commaSeen = false; + // Get and add the next property. + final PropertyNode property = propertyAssignment(); + final String key = property.getKeyName(); + final PropertyNode existingProperty = map.get(key); + + if (existingProperty == null) { + map.put(key, property); + // elements.add(property); + break; + } - if (existingProperty != null) { // ECMA section 11.1.5 Object Initialiser // point # 4 on property assignment production - final Node value = property.getValue(); - final Node getter = property.getGetter(); - final Node setter = property.getSetter(); + final Node value = property.getValue(); + final FunctionNode getter = property.getGetter(); + final FunctionNode setter = property.getSetter(); - final Node prevValue = existingProperty.getValue(); - final Node prevGetter = existingProperty.getGetter(); - final Node prevSetter = existingProperty.getSetter(); + final Node prevValue = existingProperty.getValue(); + final FunctionNode prevGetter = existingProperty.getGetter(); + final FunctionNode prevSetter = existingProperty.getSetter(); boolean redefinitionOk = true; // ECMA 11.1.5 strict mode restrictions @@ -2125,7 +1887,7 @@ loop: } final boolean isPrevAccessor = prevGetter != null || prevSetter != null; - final boolean isAccessor = getter != null || setter != null; + final boolean isAccessor = getter != null || setter != null; // data property redefined as accessor property if (prevValue != null && isAccessor) { @@ -2145,40 +1907,33 @@ loop: } if (!redefinitionOk) { - error(AbstractParser.message("property.redefinition", key.toString()), property.getToken()); + throw error(AbstractParser.message("property.redefinition", key.toString()), property.getToken()); } + PropertyNode newProperty = existingProperty; if (value != null) { - final Node existingValue = existingProperty.getValue(); - - if (existingValue == null) { - existingProperty.setValue(value); + if (prevValue == null) { + map.put(key, newProperty = newProperty.setValue(value)); } else { - final long propertyToken = Token.recast(existingProperty.getToken(), COMMARIGHT); - existingProperty.setValue(new BinaryNode(source, propertyToken, existingValue, value)); + final long propertyToken = Token.recast(newProperty.getToken(), COMMARIGHT); + map.put(key, newProperty = newProperty.setValue(new BinaryNode(source, propertyToken, prevValue, value))); } - existingProperty.setGetter(null); - existingProperty.setSetter(null); + map.put(key, newProperty = newProperty.setGetter(null).setSetter(null)); } if (getter != null) { - existingProperty.setGetter(getter); + map.put(key, newProperty = newProperty.setGetter(getter)); } if (setter != null) { - existingProperty.setSetter(setter); + map.put(key, newProperty = newProperty.setSetter(setter)); } - } else { - map.put(key, property); - elements.add(property); - } - - break; + break; } } - return new ObjectNode(source, objectToken, finish, elements); + return new ObjectNode(source, objectToken, finish, new ArrayList<Node>(map.values())); } /** @@ -2198,7 +1953,7 @@ loop: return getIdent(); case OCTAL: if (isStrictMode) { - error(AbstractParser.message("strict.no.octal"), token); + throw error(AbstractParser.message("strict.no.octal"), token); } case STRING: case ESCSTRING: @@ -2235,8 +1990,6 @@ loop: final long propertyToken = token; FunctionNode functionNode; - List<IdentNode> parameters; - PropertyNode propertyNode; PropertyKey propertyName; if (type == IDENT) { @@ -2253,11 +2006,8 @@ loop: final IdentNode getNameNode = new IdentNode(source, ((Node)getIdent).getToken(), finish, "get " + getterName); expect(LPAREN); expect(RPAREN); - parameters = new ArrayList<>(); - functionNode = functionBody(getSetToken, getNameNode, parameters, FunctionNode.Kind.GETTER); - propertyNode = new PropertyNode(source, propertyToken, finish, getIdent, null); - propertyNode.setGetter(functionNode); - return propertyNode; + functionNode = functionBody(getSetToken, getNameNode, new ArrayList<IdentNode>(), FunctionNode.Kind.GETTER); + return new PropertyNode(source, propertyToken, finish, getIdent, null, functionNode, null); case "set": final PropertyKey setIdent = propertyName(); @@ -2267,12 +2017,10 @@ loop: final IdentNode argIdent = getIdent(); verifyStrictIdent(argIdent, "setter argument"); expect(RPAREN); - parameters = new ArrayList<>(); + List<IdentNode> parameters = new ArrayList<>(); parameters.add(argIdent); functionNode = functionBody(getSetToken, setNameNode, parameters, FunctionNode.Kind.SETTER); - propertyNode = new PropertyNode(source, propertyToken, finish, setIdent, null); - propertyNode.setSetter(functionNode); - return propertyNode; + return new PropertyNode(source, propertyToken, finish, setIdent, null, null, functionNode); default: break; @@ -2286,9 +2034,7 @@ loop: expect(COLON); - final Node value = assignmentExpression(false); - propertyNode = new PropertyNode(source, propertyToken, finish, propertyName, value); - return propertyNode; + return new PropertyNode(source, propertyToken, finish, propertyName, assignmentExpression(false), null, null); } /** @@ -2321,9 +2067,6 @@ loop: } lhs = new CallNode(source, callToken, finish, lhs, arguments); - if (isInWithBlock()) { - ((CallNode)lhs).setInWithBlock(); - } } loop: @@ -2338,9 +2081,6 @@ loop: // Create call node. lhs = new CallNode(source, callToken, finish, lhs, arguments); - if (isInWithBlock()) { - ((CallNode)lhs).setInWithBlock(); - } break; @@ -2420,9 +2160,6 @@ loop: } final CallNode callNode = new CallNode(source, constructor.getToken(), finish, constructor, arguments); - if (isInWithBlock()) { - callNode.setInWithBlock(); - } return new UnaryNode(source, newToken, callNode); } @@ -2482,8 +2219,7 @@ loop: case PERIOD: if (lhs == null) { - error(AbstractParser.message("expected.operand", type.getNameOrType())); - return null; + throw error(AbstractParser.message("expected.operand", type.getNameOrType())); } next(); @@ -2585,29 +2321,33 @@ loop: } expect(LPAREN); - final List<IdentNode> parameters = formalParameterList(); - expect(RPAREN); - final FunctionNode functionNode = functionBody(functionToken, name, parameters, FunctionNode.Kind.NORMAL); + FunctionNode functionNode = functionBody(functionToken, name, parameters, FunctionNode.Kind.NORMAL); if (isStatement) { - if(topLevel) { - functionNode.setIsDeclared(); + if (topLevel) { + functionNode = functionNode.setFlag(lc, FunctionNode.IS_DECLARED); + } else if (isStrictMode) { + throw error(JSErrorType.SYNTAX_ERROR, AbstractParser.message("strict.no.func.decl.here"), functionToken); + } else if (env._function_statement == ScriptEnvironment.FunctionStatementBehavior.ERROR) { + throw error(JSErrorType.SYNTAX_ERROR, AbstractParser.message("no.func.decl.here"), functionToken); + } else if (env._function_statement == ScriptEnvironment.FunctionStatementBehavior.WARNING) { + warning(JSErrorType.SYNTAX_ERROR, AbstractParser.message("no.func.decl.here.warn"), functionToken); } - if(ARGUMENTS.tag().equals(name.getName())) { - getFunction().setDefinesArguments(); + if (ARGUMENTS.symbolName().equals(name.getName())) { + lc.setFlag(lc.getCurrentFunction(), FunctionNode.DEFINES_ARGUMENTS); } } if (isAnonymous) { - functionNode.setIsAnonymous(); + functionNode = functionNode.setFlag(lc, FunctionNode.IS_ANONYMOUS); } final int arity = parameters.size(); - final boolean strict = functionNode.isStrictMode(); + final boolean strict = functionNode.isStrict(); if (arity > 1) { final HashSet<String> parametersSet = new HashSet<>(arity); @@ -2615,39 +2355,37 @@ loop: final IdentNode parameter = parameters.get(i); String parameterName = parameter.getName(); - if (ARGUMENTS.tag().equals(parameterName)) { - functionNode.setDefinesArguments(); + if (ARGUMENTS.symbolName().equals(parameterName)) { + functionNode = functionNode.setFlag(lc, FunctionNode.DEFINES_ARGUMENTS); } if (parametersSet.contains(parameterName)) { // redefinition of parameter name if (strict) { - error(AbstractParser.message("strict.param.redefinition", parameterName), parameter.getToken()); - } else { - // rename in non-strict mode - parameterName = functionNode.uniqueName(parameterName); - final long parameterToken = parameter.getToken(); - parameters.set(i, new IdentNode(source, parameterToken, Token.descPosition(parameterToken), functionNode.uniqueName(parameterName))); + throw error(AbstractParser.message("strict.param.redefinition", parameterName), parameter.getToken()); } + // rename in non-strict mode + parameterName = functionNode.uniqueName(parameterName); + final long parameterToken = parameter.getToken(); + parameters.set(i, new IdentNode(source, parameterToken, Token.descPosition(parameterToken), functionNode.uniqueName(parameterName))); } parametersSet.add(parameterName); } } else if (arity == 1) { - if (ARGUMENTS.tag().equals(parameters.get(0).getName())) { - functionNode.setDefinesArguments(); + if (ARGUMENTS.symbolName().equals(parameters.get(0).getName())) { + functionNode = functionNode.setFlag(lc, FunctionNode.DEFINES_ARGUMENTS); } } if (isStatement) { - final VarNode varNode = new VarNode(source, functionToken, finish, name, functionNode, true); - if(topLevel) { + final VarNode varNode = new VarNode(source, functionToken, finish, name, functionNode, VarNode.IS_STATEMENT); + if (topLevel) { functionDeclarations.add(lineNumber); functionDeclarations.add(varNode); } else { - final Block block = getBlock(); - block.addStatement(lineNumber); - block.addStatement(varNode); + appendStatement(lineNumber); + appendStatement(varNode); } } @@ -2701,13 +2439,11 @@ loop: */ private FunctionNode functionBody(final long firstToken, final IdentNode ident, final List<IdentNode> parameters, final FunctionNode.Kind kind) { FunctionNode functionNode = null; + long lastToken = 0L; try { // Create a new function block. - functionNode = newFunctionBlock(ident); - functionNode.setParameters(parameters); - functionNode.setKind(kind); - functionNode.setFirstToken(firstToken); + functionNode = newFunctionNode(firstToken, ident, parameters, kind); // Nashorn extension: expression closures if (!env._no_syntax_extensions && type != LBRACE) { @@ -2720,14 +2456,12 @@ loop: // just expression as function body final Node expr = expression(); - + assert lc.getCurrentBlock() == lc.getFunctionBody(functionNode); // create a return statement - this creates code in itself and does not need to be // wrapped into an ExecuteNode - final ReturnNode returnNode = new ReturnNode(source, expr.getToken(), finish, expr, null); - - // add the return statement - functionNode.addStatement(returnNode); - functionNode.setLastToken(token); + final ReturnNode returnNode = new ReturnNode(source, expr.getToken(), finish, expr); + appendStatement(returnNode); + lastToken = token; functionNode.setFinish(Token.descPosition(token) + Token.descLength(token)); } else { @@ -2738,23 +2472,35 @@ loop: functionDeclarations = new ArrayList<>(); try { sourceElements(); - functionNode.prependStatements(functionDeclarations); + addFunctionDeclarations(functionNode); } finally { functionDeclarations = prevFunctionDecls; } - functionNode.setLastToken(token); + lastToken = token; expect(RBRACE); functionNode.setFinish(finish); } } finally { - restoreBlock(functionNode); + functionNode = restoreFunctionNode(functionNode, lastToken); } - return functionNode; } + private void addFunctionDeclarations(final FunctionNode functionNode) { + assert lc.peek() == lc.getFunctionBody(functionNode); + VarNode lastDecl = null; + for (int i = functionDeclarations.size() - 1; i >= 0; i--) { + Node decl = functionDeclarations.get(i); + if (lastDecl == null && decl instanceof VarNode) { + decl = lastDecl = ((VarNode)decl).setFlag(VarNode.IS_LAST_FUNCTION_DECLARATION); + lc.setFlag(functionNode, FunctionNode.HAS_FUNCTION_DECLARATIONS); + } + prependStatement(decl); + } + } + private RuntimeNode referenceError(final Node lhs, final Node rhs) { final ArrayList<Node> args = new ArrayList<>(); args.add(lhs); @@ -2764,9 +2510,7 @@ loop: args.add(rhs); } args.add(LiteralNode.newInstance(source, lhs.getToken(), lhs.getFinish(), lhs.toString())); - final RuntimeNode runtimeNode = new RuntimeNode(source, lhs.getToken(), - lhs.getFinish(), RuntimeNode.Request.REFERENCE_ERROR, args); - return runtimeNode; + return new RuntimeNode(source, lhs.getToken(), lhs.getFinish(), RuntimeNode.Request.REFERENCE_ERROR, args); } /* @@ -2815,19 +2559,7 @@ loop: case BIT_NOT: case NOT: next(); - final Node expr = unaryExpression(); - - /* - // Not sure if "delete <ident>" is a compile-time error or a - // runtime error in strict mode. - - if (isStrictMode) { - if (unaryTokenType == DELETE && expr instanceof IdentNode) { - error(message("strict.cant.delete.ident", ((IdentNode)expr).getName()), expr.getToken()); - } - } - */ return new UnaryNode(source, unaryToken, expr); case INCPREFIX: @@ -2890,7 +2622,7 @@ loop: } if (expression == null) { - error(AbstractParser.message("expected.operand", type.getNameOrType())); + throw error(AbstractParser.message("expected.operand", type.getNameOrType())); } return expression; @@ -2992,6 +2724,7 @@ loop: // Include commas in expression parsing. return expression(unaryExpression(), COMMARIGHT.getPrecedence(), false); } + private Node expression(final Node exprLhs, final int minPrecedence, final boolean noIn) { // Get the precedence of the next operator. int precedence = type.getPrecedence(); @@ -3087,11 +2820,26 @@ loop: return "[JavaScript Parsing]"; } - private Block getBlock() { - return lexicalContext.getCurrentBlock(); + private static void markEval(final LexicalContext lc) { + final Iterator<FunctionNode> iter = lc.getFunctions(); + boolean flaggedCurrentFn = false; + while (iter.hasNext()) { + final FunctionNode fn = iter.next(); + if (!flaggedCurrentFn) { + lc.setFlag(fn, FunctionNode.HAS_EVAL); + flaggedCurrentFn = true; + } else { + lc.setFlag(fn, FunctionNode.HAS_NESTED_EVAL); + } + lc.setFlag(lc.getFunctionBody(fn), Block.NEEDS_SCOPE); + } + } + + private void prependStatement(final Node statement) { + lc.prependStatement(statement); } - private FunctionNode getFunction() { - return lexicalContext.getCurrentFunction(); + private void appendStatement(final Node statement) { + lc.appendStatement(statement); } } diff --git a/src/jdk/nashorn/internal/parser/TokenType.java b/src/jdk/nashorn/internal/parser/TokenType.java index 9a2b7b2b..657c8c46 100644 --- a/src/jdk/nashorn/internal/parser/TokenType.java +++ b/src/jdk/nashorn/internal/parser/TokenType.java @@ -280,6 +280,11 @@ public enum TokenType { return values; } + @Override + public String toString() { + return name; + } + static { // Avoid cloning of enumeration. values = TokenType.values(); diff --git a/src/jdk/nashorn/internal/runtime/AccessorProperty.java b/src/jdk/nashorn/internal/runtime/AccessorProperty.java index 996bea35..b0ca46f7 100644 --- a/src/jdk/nashorn/internal/runtime/AccessorProperty.java +++ b/src/jdk/nashorn/internal/runtime/AccessorProperty.java @@ -54,10 +54,24 @@ import jdk.nashorn.internal.lookup.MethodHandleFactory; * @see SpillProperty */ public class AccessorProperty extends Property { + private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); private static final MethodHandle REPLACE_MAP = findOwnMH("replaceMap", Object.class, Object.class, PropertyMap.class, String.class, Class.class, Class.class); private static final int NOOF_TYPES = getNumberOfAccessorTypes(); + /** + * Properties in different maps for the same structure class will share their field getters and setters. This could + * be further extended to other method handles that are looked up in the AccessorProperty constructor, but right now + * these are the most frequently retrieved ones, and lookup of method handle natives only registers in the profiler + * for them. + */ + private static ClassValue<GettersSetters> GETTERS_SETTERS = new ClassValue<GettersSetters>() { + @Override + protected GettersSetters computeValue(Class<?> structure) { + return new GettersSetters(structure); + } + }; + /** Property getter cache */ private MethodHandle[] getters = new MethodHandle[NOOF_TYPES]; @@ -152,6 +166,22 @@ public class AccessorProperty extends Property { setCurrentType(getterType); } + private static class GettersSetters { + final MethodHandle[] getters; + final MethodHandle[] setters; + + public GettersSetters(Class<?> structure) { + final int fieldCount = ObjectClassGenerator.getFieldCount(structure); + getters = new MethodHandle[fieldCount]; + setters = new MethodHandle[fieldCount]; + for(int i = 0; i < fieldCount; ++i) { + final String fieldName = ObjectClassGenerator.getFieldName(i, Type.OBJECT); + getters[i] = MH.getter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()); + setters[i] = MH.setter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()); + } + } + } + /** * Constructor for dual field AccessorPropertys. * @@ -171,22 +201,19 @@ public class AccessorProperty extends Property { primitiveGetter = null; primitiveSetter = null; - final MethodHandles.Lookup lookup = MethodHandles.lookup(); - if (isParameter() && hasArguments()) { - final MethodHandle arguments = MH.getter(MethodHandles.lookup(), structure, "arguments", Object.class); + final MethodHandle arguments = MH.getter(lookup, structure, "arguments", Object.class); final MethodHandle argumentsSO = MH.asType(arguments, arguments.type().changeReturnType(ScriptObject.class)); objectGetter = MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, argumentsSO), 1, slot); objectSetter = MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, argumentsSO), 1, slot); } else { - final String fieldNameObject = ObjectClassGenerator.getFieldName(slot, Type.OBJECT); - final String fieldNamePrimitive = ObjectClassGenerator.getFieldName(slot, ObjectClassGenerator.PRIMITIVE_TYPE); - - objectGetter = MH.getter(lookup, structure, fieldNameObject, Type.OBJECT.getTypeClass()); - objectSetter = MH.setter(lookup, structure, fieldNameObject, Type.OBJECT.getTypeClass()); + final GettersSetters gs = GETTERS_SETTERS.get(structure); + objectGetter = gs.getters[slot]; + objectSetter = gs.setters[slot]; if (!OBJECT_FIELDS_ONLY) { + final String fieldNamePrimitive = ObjectClassGenerator.getFieldName(slot, ObjectClassGenerator.PRIMITIVE_TYPE); primitiveGetter = MH.getter(lookup, structure, fieldNamePrimitive, PRIMITIVE_TYPE.getTypeClass()); primitiveSetter = MH.setter(lookup, structure, fieldNamePrimitive, PRIMITIVE_TYPE.getTypeClass()); } @@ -365,7 +392,7 @@ public class AccessorProperty extends Property { } private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { - return MH.findStatic(MethodHandles.lookup(), AccessorProperty.class, name, MH.type(rtype, types)); + return MH.findStatic(lookup, AccessorProperty.class, name, MH.type(rtype, types)); } } diff --git a/src/jdk/nashorn/internal/runtime/Context.java b/src/jdk/nashorn/internal/runtime/Context.java index 448df143..40fe1ba1 100644 --- a/src/jdk/nashorn/internal/runtime/Context.java +++ b/src/jdk/nashorn/internal/runtime/Context.java @@ -54,9 +54,7 @@ import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.debug.ASTWriter; import jdk.nashorn.internal.ir.debug.PrintVisitor; import jdk.nashorn.internal.parser.Parser; -import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory; import jdk.nashorn.internal.runtime.options.Options; -import sun.reflect.Reflection; /** * This class manages the global state of execution. Context is immutable. @@ -114,22 +112,8 @@ public final class Context { * @return the current global scope */ public static ScriptObject getGlobal() { - final SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - // skip getCallerClass and getGlobal and get to the real caller - Class<?> caller = Reflection.getCallerClass(2); - ClassLoader callerLoader = caller.getClassLoader(); - - // Allow this method only for nashorn's own classes, objects - // package classes and Java adapter classes. Rest should - // have the necessary security permission. - if (callerLoader != myLoader && - !(callerLoader instanceof StructureLoader) && - !(JavaAdapterFactory.isAdapterClass(caller))) { - sm.checkPermission(new RuntimePermission("nashorn.getGlobal")); - } - } - + // This class in a package.access protected package. + // Trusted code only can call this method. return getGlobalTrusted(); } @@ -397,7 +381,7 @@ public final class Context { // We need to get strict mode flag from compiled class. This is // because eval code may start with "use strict" directive. try { - strictFlag = clazz.getField(STRICT_MODE.tag()).getBoolean(null); + strictFlag = clazz.getField(STRICT_MODE.symbolName()).getBoolean(null); } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { //ignored strictFlag = false; @@ -430,6 +414,28 @@ public final class Context { return ScriptRuntime.apply(func, evalThis); } + private Source loadInternal(final String srcStr, final String prefix, final String resourcePath) { + if (srcStr.startsWith(prefix)) { + final String resource = resourcePath + srcStr.substring(prefix.length()); + // NOTE: even sandbox scripts should be able to load scripts in nashorn: scheme + // These scripts are always available and are loaded from nashorn.jar's resources. + return AccessController.doPrivileged( + new PrivilegedAction<Source>() { + @Override + public Source run() { + try { + final URL resURL = Context.class.getResource(resource); + return (resURL != null)? new Source(srcStr, resURL) : null; + } catch (final IOException exp) { + return null; + } + } + }); + } + + return null; + } + /** * Implementation of {@code load} Nashorn extension. Load a script file from a source * expression @@ -442,33 +448,18 @@ public final class Context { * @throws IOException if source cannot be found or loaded */ public Object load(final ScriptObject scope, final Object from) throws IOException { - Object src = (from instanceof ConsString)? from.toString() : from; + final Object src = (from instanceof ConsString)? from.toString() : from; Source source = null; // load accepts a String (which could be a URL or a file name), a File, a URL // or a ScriptObject that has "name" and "source" (string valued) properties. if (src instanceof String) { final String srcStr = (String)src; - final File file = new File(srcStr); + final File file = new File(srcStr); if (srcStr.indexOf(':') != -1) { - if (srcStr.startsWith("nashorn:")) { - final String resource = "resources/" + srcStr.substring("nashorn:".length()); - // NOTE: even sandbox scripts should be able to load scripts in nashorn: scheme - // These scripts are always available and are loaded from nashorn.jar's resources. - source = AccessController.doPrivileged( - new PrivilegedAction<Source>() { - @Override - public Source run() { - try { - final URL resURL = Context.class.getResource(resource); - return (resURL != null)? new Source(srcStr, resURL) : null; - } catch (final IOException exp) { - return null; - } - } - }); - } else { - URL url = null; + if ((source = loadInternal(srcStr, "nashorn:", "resources/")) == null && + (source = loadInternal(srcStr, "fx:", "resources/fx/")) == null) { + URL url; try { //check for malformed url. if malformed, it may still be a valid file url = new URL(srcStr); @@ -711,7 +702,7 @@ public final class Context { MH.findStatic( MethodHandles.lookup(), script, - RUN_SCRIPT.tag(), + RUN_SCRIPT.symbolName(), MH.type( Object.class, ScriptFunction.class, @@ -720,13 +711,13 @@ public final class Context { boolean strict; try { - strict = script.getField(STRICT_MODE.tag()).getBoolean(null); + strict = script.getField(STRICT_MODE.symbolName()).getBoolean(null); } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { strict = false; } // Package as a JavaScript function and pass function back to shell. - return ((GlobalObject)Context.getGlobalTrusted()).newScriptFunction(RUN_SCRIPT.tag(), runMethodHandle, scope, strict); + return ((GlobalObject)Context.getGlobalTrusted()).newScriptFunction(RUN_SCRIPT.symbolName(), runMethodHandle, scope, strict); } private ScriptFunction compileScript(final Source source, final ScriptObject scope, final ErrorManager errMan) { @@ -744,13 +735,13 @@ public final class Context { global = (GlobalObject)Context.getGlobalTrusted(); script = global.findCachedClass(source); if (script != null) { - Compiler.LOG.fine("Code cache hit for " + source + " avoiding recompile."); + Compiler.LOG.fine("Code cache hit for ", source, " avoiding recompile."); return script; } } final FunctionNode functionNode = new Parser(env, source, errMan, strict).parse(); - if (errors.hasErrors() || env._parse_only) { + if (errors.hasErrors()) { return null; } @@ -762,6 +753,10 @@ public final class Context { getErr().println(new PrintVisitor(functionNode)); } + if (env._parse_only) { + return null; + } + final URL url = source.getURL(); final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader; final CodeSource cs = url == null ? null : new CodeSource(url, (CodeSigner[])null); diff --git a/src/jdk/nashorn/internal/runtime/DebugLogger.java b/src/jdk/nashorn/internal/runtime/DebugLogger.java index aa7f05d4..dda24bbf 100644 --- a/src/jdk/nashorn/internal/runtime/DebugLogger.java +++ b/src/jdk/nashorn/internal/runtime/DebugLogger.java @@ -135,7 +135,16 @@ public final class DebugLogger { * @param str the string to log */ public void finest(final String str) { - log(str, Level.FINEST); + log(Level.FINEST, str); + } + + /** + * Shorthand for outputting a log string as log level + * {@link java.util.logging.Level#FINEST} on this logger + * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead + */ + public void finest(final Object... objs) { + log(Level.FINEST, objs); } /** @@ -144,7 +153,16 @@ public final class DebugLogger { * @param str the string to log */ public void finer(final String str) { - log(str, Level.FINER); + log(Level.FINER, str); + } + + /** + * Shorthand for outputting a log string as log level + * {@link java.util.logging.Level#FINER} on this logger + * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead + */ + public void finer(final Object... objs) { + log(Level.FINER, objs); } /** @@ -153,7 +171,16 @@ public final class DebugLogger { * @param str the string to log */ public void fine(final String str) { - log(str, Level.FINE); + log(Level.FINE, str); + } + + /** + * Shorthand for outputting a log string as log level + * {@link java.util.logging.Level#FINE} on this logger + * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead + */ + public void fine(final Object... objs) { + log(Level.FINE, objs); } /** @@ -162,7 +189,16 @@ public final class DebugLogger { * @param str the string to log */ public void config(final String str) { - log(str, Level.CONFIG); + log(Level.CONFIG, str); + } + + /** + * Shorthand for outputting a log string as log level + * {@link java.util.logging.Level#CONFIG} on this logger + * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead + */ + public void config(final Object... objs) { + log(Level.CONFIG, objs); } /** @@ -171,7 +207,16 @@ public final class DebugLogger { * @param str the string to log */ public void info(final String str) { - log(str, Level.INFO); + log(Level.INFO, str); + } + + /** + * Shorthand for outputting a log string as log level + * {@link java.util.logging.Level#FINE} on this logger + * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead + */ + public void info(final Object... objs) { + log(Level.INFO, objs); } /** @@ -180,7 +225,16 @@ public final class DebugLogger { * @param str the string to log */ public void warning(final String str) { - log(str, Level.WARNING); + log(Level.WARNING, str); + } + + /** + * Shorthand for outputting a log string as log level + * {@link java.util.logging.Level#FINE} on this logger + * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead + */ + public void warning(final Object... objs) { + log(Level.WARNING, objs); } /** @@ -189,20 +243,28 @@ public final class DebugLogger { * @param str the string to log */ public void severe(final String str) { - log(str, Level.SEVERE); + log(Level.SEVERE, str); + } + + /** + * Shorthand for outputting a log string as log level + * {@link java.util.logging.Level#FINE} on this logger + * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead + */ + public void severe(final Object... objs) { + log(Level.SEVERE, objs); } /** * Output log line on this logger at a given level of verbosity * @see java.util.logging.Level * - * @param str string to log * @param level minimum log level required for logging to take place + * @param str string to log */ - public void log(final String str, final Level level) { + public void log(final Level level, final String str) { if (isEnabled) { final StringBuilder sb = new StringBuilder(); - for (int i = 0 ; i < indent ; i++) { sb.append(' '); } @@ -210,4 +272,24 @@ public final class DebugLogger { logger.log(level, sb.toString()); } } + + /** + * Output log line on this logger at a given level of verbosity + * @see java.util.logging.Level + * + * @param level minimum log level required for logging to take place + * @param objs objects for which to invoke toString and concatenate to log + */ + public void log(final Level level, final Object... objs) { + if (isEnabled) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0 ; i < indent ; i++) { + sb.append(' '); + } + for (final Object obj : objs) { + sb.append(obj); + } + logger.log(level, sb.toString()); + } + } } diff --git a/src/jdk/nashorn/internal/runtime/FindProperty.java b/src/jdk/nashorn/internal/runtime/FindProperty.java index 35ffed93..903d1d5d 100644 --- a/src/jdk/nashorn/internal/runtime/FindProperty.java +++ b/src/jdk/nashorn/internal/runtime/FindProperty.java @@ -105,6 +105,22 @@ public final class FindProperty { } /** + * Return the appropriate receiver for a getter. + * @return appropriate receiver + */ + public ScriptObject getGetterReceiver() { + return property != null && property.hasGetterFunction() ? self : prototype; + } + + /** + * Return the appropriate receiver for a setter. + * @return appropriate receiver + */ + public ScriptObject getSetterReceiver() { + return property != null && property.hasSetterFunction() ? self : prototype; + } + + /** * Return the property that was found * @return property */ diff --git a/src/jdk/nashorn/internal/runtime/JSONFunctions.java b/src/jdk/nashorn/internal/runtime/JSONFunctions.java index ba486be8..729df58c 100644 --- a/src/jdk/nashorn/internal/runtime/JSONFunctions.java +++ b/src/jdk/nashorn/internal/runtime/JSONFunctions.java @@ -105,9 +105,7 @@ public final class JSONFunctions { // This is the abstract "Walk" operation from the spec. private static Object walk(final ScriptObject holder, final Object name, final ScriptFunction reviver) { final Object val = holder.get(name); - if (val == ScriptRuntime.UNDEFINED) { - return val; - } else if (val instanceof ScriptObject) { + if (val instanceof ScriptObject) { final ScriptObject valueObj = (ScriptObject)val; final boolean strict = valueObj.isStrictContext(); final Iterator<String> iter = valueObj.propertyIterator(); @@ -122,33 +120,15 @@ public final class JSONFunctions { valueObj.set(key, newElement, strict); } } + } - return valueObj; - } else if (isArray(val)) { - final ScriptObject valueArray = (ScriptObject)val; - final boolean strict = valueArray.isStrictContext(); - final Iterator<String> iter = valueArray.propertyIterator(); - - while (iter.hasNext()) { - final String key = iter.next(); - final Object newElement = walk(valueArray, valueArray.get(key), reviver); - - if (newElement == ScriptRuntime.UNDEFINED) { - valueArray.delete(key, strict); - } else { - valueArray.set(key, newElement, strict); - } - } - return valueArray; - } else { - try { - // Object.class, ScriptFunction.class, ScriptObject.class, String.class, Object.class); - return REVIVER_INVOKER.invokeExact(reviver, holder, JSType.toString(name), val); - } catch(Error|RuntimeException t) { - throw t; - } catch(final Throwable t) { - throw new RuntimeException(t); - } + try { + // Object.class, ScriptFunction.class, ScriptObject.class, String.class, Object.class); + return REVIVER_INVOKER.invokeExact(reviver, holder, JSType.toString(name), val); + } catch(Error|RuntimeException t) { + throw t; + } catch(final Throwable t) { + throw new RuntimeException(t); } } diff --git a/src/jdk/nashorn/internal/runtime/JSType.java b/src/jdk/nashorn/internal/runtime/JSType.java index 21977816..e972a99e 100644 --- a/src/jdk/nashorn/internal/runtime/JSType.java +++ b/src/jdk/nashorn/internal/runtime/JSType.java @@ -102,6 +102,8 @@ public enum JSType { /** JavaScript compliant conversion function from Object to primitive */ public static final Call TO_PRIMITIVE = staticCall(JSType.class, "toPrimitive", Object.class, Object.class); + private static final double INT32_LIMIT = 4294967296.0; + /** * The external type name as returned by ECMAScript "typeof" operator * @@ -612,10 +614,7 @@ public enum JSType { * @return an int32 */ public static int toInt32(final double num) { - if (Double.isInfinite(num)) { - return 0; - } - return (int)(long)num; + return (int)doubleToInt32(num); } /** @@ -658,10 +657,7 @@ public enum JSType { * @return a uint32 */ public static long toUint32(final double num) { - if (Double.isInfinite(num)) { - return 0L; - } - return ((long)num) & 0xffff_ffffL; + return doubleToInt32(num) & MAX_UINT; } /** @@ -702,10 +698,22 @@ public enum JSType { * @return a uint16 */ public static int toUint16(final double num) { - if (Double.isInfinite(num)) { + return ((int)doubleToInt32(num)) & 0xffff; + } + + private static long doubleToInt32(final double num) { + final int exponent = Math.getExponent(num); + if (exponent < 31) { + return (long) num; // Fits into 32 bits + } + if (exponent >= 84) { + // Either infinite or NaN or so large that shift / modulo will produce 0 + // (52 bit mantissa + 32 bit target width). return 0; } - return ((int)(long)num) & 0xffff; + // This is rather slow and could probably be sped up using bit-fiddling. + final double d = (num >= 0) ? Math.floor(num) : Math.ceil(num); + return (long)(d % INT32_LIMIT); } /** diff --git a/src/jdk/nashorn/internal/runtime/NativeJavaPackage.java b/src/jdk/nashorn/internal/runtime/NativeJavaPackage.java index d75abd35..b10b72df 100644 --- a/src/jdk/nashorn/internal/runtime/NativeJavaPackage.java +++ b/src/jdk/nashorn/internal/runtime/NativeJavaPackage.java @@ -170,7 +170,7 @@ public final class NativeJavaPackage extends ScriptObject { Class<?> javaClass = null; try { javaClass = context.findClass(fullName); - } catch (final ClassNotFoundException e) { + } catch (final NoClassDefFoundError | ClassNotFoundException e) { //ignored } diff --git a/src/jdk/nashorn/internal/runtime/PropertyHashMap.java b/src/jdk/nashorn/internal/runtime/PropertyHashMap.java index 9759caa2..6e41fd56 100644 --- a/src/jdk/nashorn/internal/runtime/PropertyHashMap.java +++ b/src/jdk/nashorn/internal/runtime/PropertyHashMap.java @@ -104,10 +104,10 @@ import java.util.Set; */ public final class PropertyHashMap implements Map <String, Property> { /** Number of initial bins. Power of 2. */ - private static final int INITIAL_BINS = 16; + private static final int INITIAL_BINS = 32; /** Threshold before using bins. */ - private static final int LIST_THRESHOLD = 4; + private static final int LIST_THRESHOLD = 8; /** Initial map. */ public static final PropertyHashMap EMPTY_MAP = new PropertyHashMap(); @@ -300,8 +300,8 @@ public final class PropertyHashMap implements Map <String, Property> { * @return Number of bins required. */ private static int binsNeeded(final int n) { - // Allow for 25% padding. - return 1 << (32 - Integer.numberOfLeadingZeros((n + oneQuarter(n)) | (INITIAL_BINS - 1))); + // 50% padding + return 1 << (32 - Integer.numberOfLeadingZeros((n + (n >>> 1)) | (INITIAL_BINS - 1))); } /** @@ -316,27 +316,15 @@ public final class PropertyHashMap implements Map <String, Property> { } /** - * Used to calculate the current capacity of the bins. - * - * @param n Number of bin slots. - * - * @return 25% of n. - */ - private static int oneQuarter(final int n) { - return n >>> 2; - } - - /** * Regenerate the bin table after changing the number of bins. * * @param list // List of all properties. - * @param newSize // New size of {@link PropertyHashMap}. + * @param binSize // New size of bins. * * @return Populated bins. */ - private static Element[] rehash(final Element list, final int newSize) { - final int binsNeeded = binsNeeded(newSize); - final Element[] newBins = new Element[binsNeeded]; + private static Element[] rehash(final Element list, final int binSize) { + final Element[] newBins = new Element[binSize]; for (Element element = list; element != null; element = element.getLink()) { final Property property = element.getProperty(); final String key = property.getKey(); @@ -390,7 +378,7 @@ public final class PropertyHashMap implements Map <String, Property> { if (bins == null && newSize <= LIST_THRESHOLD) { newBins = null; } else if (newSize > threshold) { - newBins = rehash(list, newSize); + newBins = rehash(list, binsNeeded(newSize)); } else { newBins = bins.clone(); } diff --git a/src/jdk/nashorn/internal/runtime/PropertyListenerManager.java b/src/jdk/nashorn/internal/runtime/PropertyListenerManager.java index 970ccca6..a0c2f823 100644 --- a/src/jdk/nashorn/internal/runtime/PropertyListenerManager.java +++ b/src/jdk/nashorn/internal/runtime/PropertyListenerManager.java @@ -25,20 +25,20 @@ package jdk.nashorn.internal.runtime; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; /** * Helper class to manage property listeners and notification. */ public class PropertyListenerManager implements PropertyListener { + /** property listeners for this object. */ + private Map<PropertyListener,Boolean> listeners; + // These counters are updated in debug mode private static int listenersAdded; private static int listenersRemoved; - private static int listenersDead; /** * @return the listenersAdded @@ -54,16 +54,6 @@ public class PropertyListenerManager implements PropertyListener { return listenersRemoved; } - /** - * @return the listenersDead - */ - public static int getListenersDead() { - return listenersDead; - } - - /** property listeners for this object. */ - private List<WeakReference<PropertyListener>> listeners; - // Property listener management methods /** @@ -73,12 +63,13 @@ public class PropertyListenerManager implements PropertyListener { */ public final void addPropertyListener(final PropertyListener listener) { if (listeners == null) { - listeners = new ArrayList<>(); + listeners = new WeakHashMap<>(); } + if (Context.DEBUG) { listenersAdded++; } - listeners.add(new WeakReference<>(listener)); + listeners.put(listener, Boolean.TRUE); } /** @@ -88,15 +79,10 @@ public class PropertyListenerManager implements PropertyListener { */ public final void removePropertyListener(final PropertyListener listener) { if (listeners != null) { - final Iterator<WeakReference<PropertyListener>> iter = listeners.iterator(); - while (iter.hasNext()) { - if (iter.next().get() == listener) { - if (Context.DEBUG) { - listenersRemoved++; - } - iter.remove(); - } + if (Context.DEBUG) { + listenersRemoved++; } + listeners.remove(listener); } } @@ -108,18 +94,8 @@ public class PropertyListenerManager implements PropertyListener { */ protected final void notifyPropertyAdded(final ScriptObject object, final Property prop) { if (listeners != null) { - final Iterator<WeakReference<PropertyListener>> iter = listeners.iterator(); - while (iter.hasNext()) { - final WeakReference<PropertyListener> weakRef = iter.next(); - final PropertyListener listener = weakRef.get(); - if (listener == null) { - if (Context.DEBUG) { - listenersDead++; - } - iter.remove(); - } else { - listener.propertyAdded(object, prop); - } + for (PropertyListener listener : listeners.keySet()) { + listener.propertyAdded(object, prop); } } } @@ -132,18 +108,8 @@ public class PropertyListenerManager implements PropertyListener { */ protected final void notifyPropertyDeleted(final ScriptObject object, final Property prop) { if (listeners != null) { - final Iterator<WeakReference<PropertyListener>> iter = listeners.iterator(); - while (iter.hasNext()) { - final WeakReference<PropertyListener> weakRef = iter.next(); - final PropertyListener listener = weakRef.get(); - if (listener == null) { - if (Context.DEBUG) { - listenersDead++; - } - iter.remove(); - } else { - listener.propertyDeleted(object, prop); - } + for (PropertyListener listener : listeners.keySet()) { + listener.propertyDeleted(object, prop); } } } @@ -157,18 +123,8 @@ public class PropertyListenerManager implements PropertyListener { */ protected final void notifyPropertyModified(final ScriptObject object, final Property oldProp, final Property newProp) { if (listeners != null) { - final Iterator<WeakReference<PropertyListener>> iter = listeners.iterator(); - while (iter.hasNext()) { - final WeakReference<PropertyListener> weakRef = iter.next(); - final PropertyListener listener = weakRef.get(); - if (listener == null) { - if (Context.DEBUG) { - listenersDead++; - } - iter.remove(); - } else { - listener.propertyModified(object, oldProp, newProp); - } + for (PropertyListener listener : listeners.keySet()) { + listener.propertyModified(object, oldProp, newProp); } } } diff --git a/src/jdk/nashorn/internal/runtime/PropertyMap.java b/src/jdk/nashorn/internal/runtime/PropertyMap.java index 19c12b7a..b5154c85 100644 --- a/src/jdk/nashorn/internal/runtime/PropertyMap.java +++ b/src/jdk/nashorn/internal/runtime/PropertyMap.java @@ -540,11 +540,13 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { * @param newMap Modified {@link PropertyMap}. */ private void addToHistory(final Property property, final PropertyMap newMap) { - if (history == null) { - history = new LinkedHashMap<>(); - } + if (!properties.isEmpty()) { + if (history == null) { + history = new LinkedHashMap<>(); + } - history.put(property, newMap); + history.put(property, newMap); + } } /** diff --git a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java index 03f0ab5d..c2259896 100644 --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java @@ -25,10 +25,11 @@ package jdk.nashorn.internal.runtime; +import static jdk.nashorn.internal.lookup.Lookup.MH; + import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; - import jdk.nashorn.internal.codegen.Compiler; import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.FunctionSignature; @@ -37,8 +38,6 @@ import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.parser.Token; import jdk.nashorn.internal.parser.TokenType; -import static jdk.nashorn.internal.lookup.Lookup.MH; - /** * This is a subclass that represents a script function that may be regenerated, * for example with specialization based on call site types, or lazily generated. @@ -47,7 +46,7 @@ import static jdk.nashorn.internal.lookup.Lookup.MH; */ public final class RecompilableScriptFunctionData extends ScriptFunctionData { - private final FunctionNode functionNode; + private FunctionNode functionNode; private final PropertyMap allocatorMap; private final CodeInstaller<ScriptEnvironment> installer; private final String allocatorClassName; @@ -70,7 +69,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData { "" : functionNode.getIdent().getName(), functionNode.getParameters().size(), - functionNode.isStrictMode(), + functionNode.isStrict(), false, true); @@ -129,7 +128,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData { private void ensureHasAllocator() throws ClassNotFoundException { if (allocator == null && allocatorClassName != null) { - this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.tag(), MH.type(ScriptObject.class, PropertyMap.class)); + this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class)); } } @@ -148,8 +147,11 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData { // therefore, currently method specialization is disabled. TODO if (functionNode.isLazy()) { - Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '" + functionNode.getName() + "'"); - new Compiler(installer, functionNode).compile().install(); + Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '", functionNode.getName(), "'"); + final Compiler compiler = new Compiler(installer, functionNode); + functionNode = compiler.compile(); + assert !functionNode.isLazy(); + compiler.install(); // we don't need to update any flags - varArgs and needsCallee are instrincic // in the function world we need to get a destination node from the compile instead @@ -159,7 +161,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData { // we can't get here unless we have bytecode, either from eager compilation or from // running a lazy compile on the lines above - assert functionNode.hasState(CompilationState.INSTALLED); + assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " " + functionNode.getState() + " " + Debug.id(functionNode); // code exists - look it up and add it into the automatically sorted invoker list code.add( diff --git a/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java b/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java index 36a1d2ac..74fab0ce 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java +++ b/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java @@ -82,6 +82,36 @@ public final class ScriptEnvironment { /** Show full Nashorn version */ public final boolean _fullversion; + /** Launch using as fx application */ + public final boolean _fx; + + /** + * Behavior when encountering a function declaration in a lexical context where only statements are acceptable + * (function declarations are source elements, but not statements). + */ + public enum FunctionStatementBehavior { + /** + * Accept the function declaration silently and treat it as if it were a function expression assigned to a local + * variable. + */ + ACCEPT, + /** + * Log a parser warning, but accept the function declaration and treat it as if it were a function expression + * assigned to a local variable. + */ + WARNING, + /** + * Raise a {@code SyntaxError}. + */ + ERROR + } + + /** + * Behavior when encountering a function declaration in a lexical context where only statements are acceptable + * (function declarations are source elements, but not statements). + */ + public final FunctionStatementBehavior _function_statement; + /** Should lazy compilation take place */ public final boolean _lazy_compilation; @@ -158,6 +188,14 @@ public final class ScriptEnvironment { _early_lvalue_error = options.getBoolean("early.lvalue.error"); _empty_statements = options.getBoolean("empty.statements"); _fullversion = options.getBoolean("fullversion"); + if(options.getBoolean("function.statement.error")) { + _function_statement = FunctionStatementBehavior.ERROR; + } else if(options.getBoolean("function.statement.warning")) { + _function_statement = FunctionStatementBehavior.WARNING; + } else { + _function_statement = FunctionStatementBehavior.ACCEPT; + } + _fx = options.getBoolean("fx"); _lazy_compilation = options.getBoolean("lazy.compilation"); _loader_per_compile = options.getBoolean("loader.per.compile"); _no_syntax_extensions = options.getBoolean("no.syntax.extensions"); diff --git a/src/jdk/nashorn/internal/runtime/ScriptFunction.java b/src/jdk/nashorn/internal/runtime/ScriptFunction.java index 512a0b16..26e6270a 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptFunction.java +++ b/src/jdk/nashorn/internal/runtime/ScriptFunction.java @@ -71,9 +71,6 @@ public abstract class ScriptFunction extends ScriptObject { private static final MethodHandle IS_NONSTRICT_FUNCTION = findOwnMH("isNonStrictFunction", boolean.class, Object.class, Object.class, ScriptFunctionData.class); - /** Reference to constructor prototype. */ - protected Object prototype; - /** The parent scope. */ private final ScriptObject scope; @@ -221,6 +218,7 @@ public abstract class ScriptFunction extends ScriptObject { final ScriptObject object = data.allocate(); if (object != null) { + Object prototype = getPrototype(); if (prototype instanceof ScriptObject) { object.setProto((ScriptObject)prototype); } @@ -282,24 +280,18 @@ public abstract class ScriptFunction extends ScriptObject { * Get the prototype object for this function * @return prototype */ - public final Object getPrototype() { - return prototype; - } + public abstract Object getPrototype(); /** * Set the prototype object for this function * @param prototype new prototype object - * @return the prototype parameter */ - public final Object setPrototype(final Object prototype) { - this.prototype = prototype; - return prototype; - } + public abstract void setPrototype(Object prototype); /** * Return the most appropriate invoke handle if there are specializations * @param type most specific method type to look for invocation with - * @param callsite args for trampoline invocation + * @param args args for trampoline invocation * @return invoke method handle */ private MethodHandle getBestInvoker(final MethodType type, final Object[] args) { diff --git a/src/jdk/nashorn/internal/runtime/ScriptObject.java b/src/jdk/nashorn/internal/runtime/ScriptObject.java index 1942ec22..93517201 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptObject.java +++ b/src/jdk/nashorn/internal/runtime/ScriptObject.java @@ -28,6 +28,7 @@ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall; import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; +import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.PropertyDescriptor.CONFIGURABLE; @@ -39,7 +40,6 @@ import static jdk.nashorn.internal.runtime.PropertyDescriptor.WRITABLE; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndexNoThrow; import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; -import static jdk.nashorn.internal.lookup.Lookup.MH; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -61,12 +61,13 @@ import jdk.internal.dynalink.linker.LinkRequest; import jdk.internal.dynalink.support.CallSiteDescriptorFactory; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.codegen.ObjectClassGenerator; +import jdk.nashorn.internal.lookup.Lookup; +import jdk.nashorn.internal.lookup.MethodHandleFactory; import jdk.nashorn.internal.objects.AccessorPropertyDescriptor; import jdk.nashorn.internal.objects.DataPropertyDescriptor; import jdk.nashorn.internal.runtime.arrays.ArrayData; import jdk.nashorn.internal.runtime.linker.Bootstrap; -import jdk.nashorn.internal.lookup.Lookup; -import jdk.nashorn.internal.lookup.MethodHandleFactory; +import jdk.nashorn.internal.runtime.linker.LinkerCallSite; import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; import jdk.nashorn.internal.runtime.linker.NashornGuards; @@ -901,7 +902,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr final MethodHandle getter = find.getGetter(int.class); if (getter != null) { try { - return (int)getter.invokeExact((Object)find.getOwner()); + return (int)getter.invokeExact((Object)find.getGetterReceiver()); } catch (final Error|RuntimeException e) { throw e; } catch (final Throwable e) { @@ -916,7 +917,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr final MethodHandle getter = find.getGetter(long.class); if (getter != null) { try { - return (long)getter.invokeExact((Object)find.getOwner()); + return (long)getter.invokeExact((Object)find.getGetterReceiver()); } catch (final Error|RuntimeException e) { throw e; } catch (final Throwable e) { @@ -931,7 +932,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr final MethodHandle getter = find.getGetter(double.class); if (getter != null) { try { - return (double)getter.invokeExact((Object)find.getOwner()); + return (double)getter.invokeExact((Object)find.getGetterReceiver()); } catch (final Error|RuntimeException e) { throw e; } catch (final Throwable e) { @@ -953,7 +954,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr final MethodHandle getter = find.getGetter(Object.class); if (getter != null) { try { - return getter.invokeExact((Object)find.getOwner()); + return getter.invokeExact((Object)find.getGetterReceiver()); } catch (final Error|RuntimeException e) { throw e; } catch (final Throwable e) { @@ -1679,12 +1680,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr * @return GuardedInvocation to be invoked at call site. */ protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) { - final String name = desc.getNameToken(2); - - if (request.isCallSiteUnstable()) { - return findMegaMorphicGetMethod(desc, name); - } - + final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); final FindProperty find = findProperty(name, true); MethodHandle methodHandle; @@ -1700,6 +1696,10 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr throw new AssertionError(); // never invoked with any other operation } + if (request.isCallSiteUnstable()) { + return findMegaMorphicGetMethod(desc, name); + } + final Class<?> returnType = desc.getMethodType().returnType(); final Property property = find.getProperty(); methodHandle = find.getGetter(returnType); @@ -1727,7 +1727,9 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr } private static GuardedInvocation findMegaMorphicGetMethod(final CallSiteDescriptor desc, final String name) { - final GuardedInvocation inv = findGetIndexMethod(desc.getMethodType().insertParameterTypes(1, Object.class)); + final MethodType mhType = desc.getMethodType().insertParameterTypes(1, Object.class); + final GuardedInvocation inv = findGetIndexMethod(mhType); + return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard()); } @@ -1890,8 +1892,8 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr } private static GuardedInvocation findMegaMorphicSetMethod(final CallSiteDescriptor desc, final String name) { - final GuardedInvocation inv = findSetIndexMethod(desc.getMethodType().insertParameterTypes(1, Object.class), - NashornCallSiteDescriptor.isStrict(desc)); + final MethodType type = desc.getMethodType().insertParameterTypes(1, Object.class); + final GuardedInvocation inv = findSetIndexMethod(type, NashornCallSiteDescriptor.isStrict(desc)); return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard()); } @@ -1949,7 +1951,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr * @return GuardedInvocation to be invoked at call site. */ public GuardedInvocation noSuchProperty(final CallSiteDescriptor desc, final LinkRequest request) { - final String name = desc.getNameToken(2); + final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); final boolean scopeAccess = isScope() && NashornCallSiteDescriptor.isScope(desc); @@ -1973,6 +1975,24 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr return createEmptyGetter(desc, name); } + /** + * Invoke fall back if a property is not found. + * @param name Name of property. + * @return Result from call. + */ + private Object invokeNoSuchProperty(final String name) { + final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); + + if (find != null) { + final Object func = getObjectValue(find); + + if (func instanceof ScriptFunction) { + return ScriptRuntime.apply((ScriptFunction)func, this, name); + } + } + + return UNDEFINED; + } private GuardedInvocation createEmptyGetter(final CallSiteDescriptor desc, final String name) { return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), getMap().getProtoGetSwitchPoint(name), NashornGuards.getMapGuard(getMap())); @@ -2120,8 +2140,11 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr * * Make sure arguments are paired correctly. * @param methodHandle MethodHandle to adjust. - * @param callType MethodType of caller. - * @param callerVarArg true if the caller is vararg, false otherwise, null if it should be inferred. + * @param callType MethodType of the call site. + * @param callerVarArg true if the caller is vararg, false otherwise, null if it should be inferred from the + * {@code callType}; basically, if the last parameter type of the call site is an array, it'll be considered a + * variable arity call site. These are ordinarily rare; Nashorn code generator creates variable arity call sites + * when the call has more than {@link LinkerCallSite#ARGLIMIT} parameters. * * @return method handle with adjusted arguments */ @@ -2136,7 +2159,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr final int callCount = callType.parameterCount(); final boolean isCalleeVarArg = parameterCount > 0 && methodType.parameterType(parameterCount - 1).isArray(); - final boolean isCallerVarArg = callerVarArg != null ? callerVarArg.booleanValue() : (callCount > 1 && + final boolean isCallerVarArg = callerVarArg != null ? callerVarArg.booleanValue() : (callCount > 0 && callType.parameterType(callCount - 1).isArray()); if (callCount < parameterCount) { @@ -2239,310 +2262,314 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr setArray(getArray().shrink(newLength)); getArray().setLength(newLength); } - } + } - @Override - public int getInt(final Object key) { - final int index = getArrayIndexNoThrow(key); + private int getInt(final int index, final String key) { + if (isValidArrayIndex(index)) { + for (ScriptObject object = this; ; ) { + final FindProperty find = object.findProperty(key, false, false, this); - if (getArray().has(index)) { - return getArray().getInt(index); - } + if (find != null) { + return getIntValue(find); + } - final FindProperty find = findProperty(convertKey(key), false); + if ((object = object.getProto()) == null) { + break; + } - if (find != null) { - return getIntValue(find); - } + final ArrayData array = object.getArray(); + + if (array.has(index)) { + return array.getInt(index); + } + } + } else { + final FindProperty find = findProperty(key, true); - final ScriptObject proto = this.getProto(); + if (find != null) { + return getIntValue(find); + } + } - return proto != null ? proto.getInt(key) : 0; + return JSType.toInt32(invokeNoSuchProperty(key)); } @Override - public int getInt(final double key) { + public int getInt(final Object key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getInt(index); + if (array.has(index)) { + return array.getInt(index); } - final FindProperty find = findProperty(convertKey(key), false); + return getInt(index, convertKey(key)); + } - if (find != null) { - return getIntValue(find); - } + @Override + public int getInt(final double key) { + final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - final ScriptObject proto = this.getProto(); + if (array.has(index)) { + return array.getInt(index); + } - return proto != null ? proto.getInt(key) : 0; + return getInt(index, convertKey(key)); } @Override public int getInt(final long key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getInt(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getIntValue(find); + if (array.has(index)) { + return array.getInt(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.getInt(key) : 0; + return getInt(index, convertKey(key)); } @Override public int getInt(final int key) { - final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getInt(index); + if (array.has(key)) { + return array.getInt(key); } - final FindProperty find = findProperty(convertKey(key), false); + return getInt(key, convertKey(key)); + } - if (find != null) { - return getIntValue(find); - } + private long getLong(final int index, final String key) { + if (isValidArrayIndex(index)) { + for (ScriptObject object = this; ; ) { + final FindProperty find = object.findProperty(key, false, false, this); - final ScriptObject proto = this.getProto(); + if (find != null) { + return getLongValue(find); + } - return proto != null ? proto.getInt(key) : 0; + if ((object = object.getProto()) == null) { + break; + } + + final ArrayData array = object.getArray(); + + if (array.has(index)) { + return array.getLong(index); + } + } + } else { + final FindProperty find = findProperty(key, true); + + if (find != null) { + return getLongValue(find); + } + } + + return JSType.toLong(invokeNoSuchProperty(key)); } @Override public long getLong(final Object key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getLong(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getLongValue(find); + if (array.has(index)) { + return array.getLong(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.getLong(key) : 0L; + return getLong(index, convertKey(key)); } @Override public long getLong(final double key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getLong(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getLongValue(find); + if (array.has(index)) { + return array.getLong(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.getLong(key) : 0L; + return getLong(index, convertKey(key)); } @Override public long getLong(final long key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getLong(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getLongValue(find); + if (array.has(index)) { + return array.getLong(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.getLong(key) : 0L; + return getLong(index, convertKey(key)); } @Override public long getLong(final int key) { - final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getLong(index); + if (array.has(key)) { + return array.getLong(key); } - final FindProperty find = findProperty(convertKey(key), false); + return getLong(key, convertKey(key)); + } - if (find != null) { - return getLongValue(find); - } + private double getDouble(final int index, final String key) { + if (isValidArrayIndex(index)) { + for (ScriptObject object = this; ; ) { + final FindProperty find = object.findProperty(key, false, false, this); - final ScriptObject proto = this.getProto(); + if (find != null) { + return getDoubleValue(find); + } + + if ((object = object.getProto()) == null) { + break; + } + + final ArrayData array = object.getArray(); + + if (array.has(index)) { + return array.getDouble(index); + } + } + } else { + final FindProperty find = findProperty(key, true); - return proto != null ? proto.getLong(key) : 0L; + if (find != null) { + return getDoubleValue(find); + } + } + + return JSType.toNumber(invokeNoSuchProperty(key)); } @Override public double getDouble(final Object key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getDouble(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getDoubleValue(find); + if (array.has(index)) { + return array.getDouble(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.getDouble(key) : Double.NaN; + return getDouble(index, convertKey(key)); } @Override public double getDouble(final double key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getDouble(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getDoubleValue(find); + if (array.has(index)) { + return array.getDouble(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.getDouble(key) : Double.NaN; + return getDouble(index, convertKey(key)); } @Override public double getDouble(final long key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getDouble(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getDoubleValue(find); + if (array.has(index)) { + return array.getDouble(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.getDouble(key) : Double.NaN; + return getDouble(index, convertKey(key)); } @Override public double getDouble(final int key) { - final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getDouble(index); + if (array.has(key)) { + return array.getDouble(key); } - final FindProperty find = findProperty(convertKey(key), false); + return getDouble(key, convertKey(key)); + } - if (find != null) { - return getDoubleValue(find); - } + private Object get(final int index, final String key) { + if (isValidArrayIndex(index)) { + for (ScriptObject object = this; ; ) { + final FindProperty find = object.findProperty(key, false, false, this); + + if (find != null) { + return getObjectValue(find); + } + + if ((object = object.getProto()) == null) { + break; + } + + final ArrayData array = object.getArray(); + + if (array.has(index)) { + return array.getObject(index); + } + } + } else { + final FindProperty find = findProperty(key, true); - final ScriptObject proto = this.getProto(); + if (find != null) { + return getObjectValue(find); + } + } - return proto != null ? proto.getDouble(key) : Double.NaN; + return invokeNoSuchProperty(key); } @Override public Object get(final Object key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getObject(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getObjectValue(find); + if (array.has(index)) { + return array.getObject(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.get(key) : UNDEFINED; + return get(index, convertKey(key)); } @Override public Object get(final double key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getObject(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getObjectValue(find); + if (array.has(index)) { + return array.getObject(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.get(key) : UNDEFINED; + return get(index, convertKey(key)); } @Override public Object get(final long key) { final int index = getArrayIndexNoThrow(key); + final ArrayData array = getArray(); - if (getArray().has(index)) { - return getArray().getObject(index); - } - - final FindProperty find = findProperty(convertKey(key), false); - - if (find != null) { - return getObjectValue(find); + if (array.has(index)) { + return array.getObject(index); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.get(key) : UNDEFINED; + return get(index, convertKey(key)); } @Override public Object get(final int key) { - final int index = getArrayIndexNoThrow(key); - - if (getArray().has(index)) { - return getArray().getObject(index); - } - - final FindProperty find = findProperty(convertKey(key), false); + final ArrayData array = getArray(); - if (find != null) { - return getObjectValue(find); + if (array.has(key)) { + return array.getObject(key); } - final ScriptObject proto = this.getProto(); - - return proto != null ? proto.get(key) : UNDEFINED; + return get(key, convertKey(key)); } /** @@ -2554,7 +2581,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr */ private void doesNotHave(final int index, final Object value, final boolean strict) { final long oldLength = getArray().length(); - final long longIndex = index & 0xffff_ffffL; + final long longIndex = index & JSType.MAX_UINT; if (!getArray().has(index)) { final String key = convertKey(longIndex); @@ -2613,8 +2640,6 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr f = null; } - MethodHandle setter; - if (f != null) { if (!f.getProperty().isWritable()) { if (strict) { @@ -2624,9 +2649,9 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr return; } - setter = f.getSetter(Object.class, strict); //TODO specfields try { - setter.invokeExact((Object)f.getOwner(), value); + final MethodHandle setter = f.getSetter(Object.class, strict); //TODO specfields + setter.invokeExact((Object)f.getSetterReceiver(), value); } catch (final Error|RuntimeException e) { throw e; } catch (final Throwable e) { diff --git a/src/jdk/nashorn/internal/runtime/StructureLoader.java b/src/jdk/nashorn/internal/runtime/StructureLoader.java index 35786b0b..db55fff9 100644 --- a/src/jdk/nashorn/internal/runtime/StructureLoader.java +++ b/src/jdk/nashorn/internal/runtime/StructureLoader.java @@ -47,7 +47,7 @@ import jdk.nashorn.internal.codegen.ObjectClassGenerator; * */ final class StructureLoader extends NashornLoader { - private static final String JS_OBJECT_PREFIX_EXTERNAL = binaryName(SCRIPTS_PACKAGE) + '.' + JS_OBJECT_PREFIX.tag(); + private static final String JS_OBJECT_PREFIX_EXTERNAL = binaryName(SCRIPTS_PACKAGE) + '.' + JS_OBJECT_PREFIX.symbolName(); private static final String OBJECTS_PACKAGE_EXTERNAL = binaryName(OBJECTS_PACKAGE); /** @@ -110,7 +110,7 @@ final class StructureLoader extends NashornLoader { @Override protected Class<?> findClass(final String name) throws ClassNotFoundException { if (name.startsWith(JS_OBJECT_PREFIX_EXTERNAL)) { - final int start = name.indexOf(JS_OBJECT_PREFIX.tag()) + JS_OBJECT_PREFIX.tag().length(); + final int start = name.indexOf(JS_OBJECT_PREFIX.symbolName()) + JS_OBJECT_PREFIX.symbolName().length(); return generateClass(name, name.substring(start, name.length())); } return super.findClass(name); diff --git a/src/jdk/nashorn/internal/runtime/WithObject.java b/src/jdk/nashorn/internal/runtime/WithObject.java index 5eaf5f50..ca3fcac9 100644 --- a/src/jdk/nashorn/internal/runtime/WithObject.java +++ b/src/jdk/nashorn/internal/runtime/WithObject.java @@ -29,6 +29,7 @@ import static jdk.nashorn.internal.lookup.Lookup.MH; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; @@ -42,9 +43,10 @@ import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; */ public final class WithObject extends ScriptObject implements Scope { - private static final MethodHandle WITHEXPRESSIONFILTER = findOwnMH("withFilterExpression", Object.class, Object.class); - private static final MethodHandle WITHSCOPEFILTER = findOwnMH("withFilterScope", Object.class, Object.class); - private static final MethodHandle BIND_TO_EXPRESSION = findOwnMH("bindToExpression", Object.class, Object.class, Object.class); + private static final MethodHandle WITHEXPRESSIONFILTER = findOwnMH("withFilterExpression", Object.class, Object.class); + private static final MethodHandle WITHSCOPEFILTER = findOwnMH("withFilterScope", Object.class, Object.class); + private static final MethodHandle BIND_TO_EXPRESSION_OBJ = findOwnMH("bindToExpression", Object.class, Object.class, Object.class); + private static final MethodHandle BIND_TO_EXPRESSION_FN = findOwnMH("bindToExpression", Object.class, ScriptFunction.class, Object.class); /** With expression object. */ private final Object expression; @@ -230,22 +232,35 @@ public final class WithObject extends ScriptObject implements Scope { return (Scope) proto; } + private static GuardedInvocation fixReceiverType(final GuardedInvocation link, final MethodHandle filter) { + // The receiver may be an Object or a ScriptObject. + final MethodType invType = link.getInvocation().type(); + final MethodType newInvType = invType.changeParameterType(0, filter.type().returnType()); + return link.asType(newInvType); + } + private static GuardedInvocation fixExpressionCallSite(final NashornCallSiteDescriptor desc, final GuardedInvocation link) { // If it's not a getMethod, just add an expression filter that converts WithObject in "this" position to its // expression. if(!"getMethod".equals(desc.getFirstOperator())) { - return link.filterArguments(0, WITHEXPRESSIONFILTER); + return fixReceiverType(link, WITHEXPRESSIONFILTER).filterArguments(0, WITHEXPRESSIONFILTER); } + final MethodHandle linkInvocation = link.getInvocation(); + final MethodType linkType = linkInvocation.type(); + final boolean linkReturnsFunction = ScriptFunction.class.isAssignableFrom(linkType.returnType()); return link.replaceMethods( // Make sure getMethod will bind the script functions it receives to WithObject.expression - MH.foldArguments(BIND_TO_EXPRESSION, filter(link.getInvocation(), WITHEXPRESSIONFILTER)), + MH.foldArguments(linkReturnsFunction ? BIND_TO_EXPRESSION_FN : BIND_TO_EXPRESSION_OBJ, + filter(linkInvocation.asType(linkType.changeReturnType( + linkReturnsFunction ? ScriptFunction.class : Object.class)), WITHEXPRESSIONFILTER)), // No clever things for the guard -- it is still identically filtered. filterGuard(link, WITHEXPRESSIONFILTER)); } private static GuardedInvocation fixScopeCallSite(final GuardedInvocation link) { - return link.replaceMethods(filter(link.getInvocation(), WITHSCOPEFILTER), filterGuard(link, WITHSCOPEFILTER)); + final GuardedInvocation newLink = fixReceiverType(link, WITHSCOPEFILTER); + return link.replaceMethods(filter(newLink.getInvocation(), WITHSCOPEFILTER), filterGuard(newLink, WITHSCOPEFILTER)); } private static MethodHandle filterGuard(final GuardedInvocation link, final MethodHandle filter) { @@ -269,7 +284,11 @@ public final class WithObject extends ScriptObject implements Scope { @SuppressWarnings("unused") private static Object bindToExpression(final Object fn, final Object receiver) { - return fn instanceof ScriptFunction ? ((ScriptFunction) fn).makeBoundFunction(withFilterExpression(receiver), new Object[0]) : fn; + return fn instanceof ScriptFunction ? bindToExpression((ScriptFunction) fn, receiver) : fn; + } + + private static Object bindToExpression(final ScriptFunction fn, final Object receiver) { + return fn.makeBoundFunction(withFilterExpression(receiver), new Object[0]); } /** diff --git a/src/jdk/nashorn/internal/runtime/arrays/ArrayIndex.java b/src/jdk/nashorn/internal/runtime/arrays/ArrayIndex.java index c7de94bf..531c56b5 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/ArrayIndex.java +++ b/src/jdk/nashorn/internal/runtime/arrays/ArrayIndex.java @@ -84,7 +84,9 @@ public final class ArrayIndex { * @return valid array index, or negative value if not valid */ public static int getArrayIndexNoThrow(final Object key) { - if (key instanceof Number) { + if (key instanceof Integer) { + return getArrayIndexNoThrow(((Integer)key).intValue()); + } else if (key instanceof Number) { return getArrayIndexNoThrow(((Number)key).doubleValue()); } else if (key instanceof String) { return (int)fromString((String)key); diff --git a/src/jdk/nashorn/internal/runtime/arrays/MapIterator.java b/src/jdk/nashorn/internal/runtime/arrays/MapIterator.java index be9bc23d..fdf9d3a4 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/MapIterator.java +++ b/src/jdk/nashorn/internal/runtime/arrays/MapIterator.java @@ -66,8 +66,7 @@ class MapIterator extends ArrayLikeIterator<Object> { bumpIndex(); } - // special case - balk at iterating to infinity or MAX_UINT - return (length != JSType.MAX_UINT) && indexInArray(); + return indexInArray(); } @Override diff --git a/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java index e5ba1cd9..04ac661f 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java @@ -273,7 +273,7 @@ class SparseArrayData extends ArrayData { } private static Long indexToKey(final int index) { - return Long.valueOf(index & 0xffff_ffffL); + return Long.valueOf(index & JSType.MAX_UINT); } @Override diff --git a/src/jdk/nashorn/internal/runtime/linker/AdaptationException.java b/src/jdk/nashorn/internal/runtime/linker/AdaptationException.java new file mode 100644 index 00000000..33429dd1 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/AdaptationException.java @@ -0,0 +1,39 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.runtime.linker; + +@SuppressWarnings("serial") +class AdaptationException extends Exception { + private final AdaptationResult adaptationResult; + + AdaptationException(final AdaptationResult.Outcome outcome, final String classList) { + this.adaptationResult = new AdaptationResult(outcome, classList); + } + + AdaptationResult getAdaptationResult() { + return adaptationResult; + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/AdaptationResult.java b/src/jdk/nashorn/internal/runtime/linker/AdaptationResult.java new file mode 100644 index 00000000..5185a955 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/AdaptationResult.java @@ -0,0 +1,74 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.runtime.linker; + +import jdk.nashorn.internal.runtime.ECMAErrors; +import jdk.nashorn.internal.runtime.ECMAException; + +/** + * A result of generating an adapter for a class. A tuple of an outcome and - in case of an error outcome - a list of + * classes that caused the error. + */ +class AdaptationResult { + /** + * Contains various outcomes for attempting to generate an adapter class. These are stored in AdapterInfo instances. + * We have a successful outcome (adapter class was generated) and four possible error outcomes: superclass is final, + * superclass is not public, superclass has no public or protected constructor, more than one superclass was + * specified. We don't throw exceptions when we try to generate the adapter, but rather just record these error + * conditions as they are still useful as partial outcomes, as Nashorn's linker can still successfully check whether + * the class can be autoconverted from a script function even when it is not possible to generate an adapter for it. + */ + enum Outcome { + SUCCESS, + ERROR_FINAL_CLASS, + ERROR_NON_PUBLIC_CLASS, + ERROR_NO_ACCESSIBLE_CONSTRUCTOR, + ERROR_MULTIPLE_SUPERCLASSES, + ERROR_NO_COMMON_LOADER + } + + static final AdaptationResult SUCCESSFUL_RESULT = new AdaptationResult(Outcome.SUCCESS, ""); + + private final Outcome outcome; + private final String classList; + + AdaptationResult(final Outcome outcome, final String classList) { + this.outcome = outcome; + this.classList = classList; + } + + Outcome getOutcome() { + return outcome; + } + + String getClassList() { + return classList; + } + + ECMAException typeError() { + return ECMAErrors.typeError("extend." + outcome, classList); + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java b/src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java new file mode 100644 index 00000000..d12df47a --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java @@ -0,0 +1,188 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.runtime.linker; + +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its + * equals/hashCode is defined in terms of the identity of the class loader. The rationale for this class is that it + * couples a class loader with a random representative class coming from that loader - this representative class is then + * used to determine if one loader can see the other loader's classes. + */ +final class ClassAndLoader { + private final Class<?> representativeClass; + // Don't access this directly; most of the time, use getRetrievedLoader(), or if you know what you're doing, + // getLoader(). + private ClassLoader loader; + // We have mild affinity against eagerly retrieving the loader, as we need to do it in a privileged block. For + // the most basic case of looking up an already-generated adapter info for a single type, we avoid it. + private boolean loaderRetrieved; + + ClassAndLoader(final Class<?> representativeClass, final boolean retrieveLoader) { + this.representativeClass = representativeClass; + if(retrieveLoader) { + retrieveLoader(); + } + } + + Class<?> getRepresentativeClass() { + return representativeClass; + } + + boolean canSee(ClassAndLoader other) { + try { + final Class<?> otherClass = other.getRepresentativeClass(); + return Class.forName(otherClass.getName(), false, getLoader()) == otherClass; + } catch (final ClassNotFoundException e) { + return false; + } + } + + ClassLoader getLoader() { + if(!loaderRetrieved) { + retrieveLoader(); + } + return getRetrievedLoader(); + } + + ClassLoader getRetrievedLoader() { + assert loaderRetrieved; + return loader; + } + + private void retrieveLoader() { + loader = representativeClass.getClassLoader(); + loaderRetrieved = true; + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof ClassAndLoader && ((ClassAndLoader)obj).getRetrievedLoader() == getRetrievedLoader(); + } + + @Override + public int hashCode() { + return System.identityHashCode(getRetrievedLoader()); + } + + /** + * Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the + * list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a + * class loader that can also see all other types is returned. If there is no such loader, an exception is thrown. + * @param types the input types + * @return the first type from the array that is defined in a class loader that can also see all other types. + */ + static ClassAndLoader getDefiningClassAndLoader(final Class<?>[] types) { + // Short circuit the cheap case + if(types.length == 1) { + return new ClassAndLoader(types[0], false); + } + + return AccessController.doPrivileged(new PrivilegedAction<ClassAndLoader>() { + @Override + public ClassAndLoader run() { + return getDefiningClassAndLoaderPrivileged(types); + } + }); + } + + static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class<?>[] types) { + final Collection<ClassAndLoader> maximumVisibilityLoaders = getMaximumVisibilityLoaders(types); + + final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator(); + if(maximumVisibilityLoaders.size() == 1) { + // Fortunate case - single maximally specific class loader; return its representative class. + return it.next(); + } + + // Ambiguity; throw an error. + assert maximumVisibilityLoaders.size() > 1; // basically, can't be zero + final StringBuilder b = new StringBuilder(); + b.append(it.next().getRepresentativeClass().getCanonicalName()); + while(it.hasNext()) { + b.append(", ").append(it.next().getRepresentativeClass().getCanonicalName()); + } + throw typeError("extend.ambiguous.defining.class", b.toString()); + } + + /** + * Given an array of types, return a subset of their class loaders that are maximal according to the + * "can see other loaders' classes" relation, which is presumed to be a partial ordering. + * @param types types + * @return a collection of maximum visibility class loaders. It is guaranteed to have at least one element. + */ + private static Collection<ClassAndLoader> getMaximumVisibilityLoaders(final Class<?>[] types) { + final List<ClassAndLoader> maximumVisibilityLoaders = new LinkedList<>(); + outer: for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) { + final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator(); + while(it.hasNext()) { + final ClassAndLoader existingMax = it.next(); + final boolean candidateSeesExisting = maxCandidate.canSee(existingMax); + final boolean exitingSeesCandidate = existingMax.canSee(maxCandidate); + if(candidateSeesExisting) { + if(!exitingSeesCandidate) { + // The candidate sees the the existing maximum, so drop the existing one as it's no longer maximal. + it.remove(); + } + // NOTE: there's also the anomalous case where both loaders see each other. Not sure what to do + // about that one, as two distinct class loaders both seeing each other's classes is weird and + // violates the assumption that the relation "sees others' classes" is a partial ordering. We'll + // just not do anything, and treat them as incomparable; hopefully some later class loader that + // comes along can eliminate both of them, if it can not, we'll end up with ambiguity anyway and + // throw an error at the end. + } else if(exitingSeesCandidate) { + // Existing sees the candidate, so drop the candidate. + continue outer; + } + } + // If we get here, no existing maximum visibility loader could see the candidate, so the candidate is a new + // maximum. + maximumVisibilityLoaders.add(maxCandidate); + } + return maximumVisibilityLoaders; + } + + private static Collection<ClassAndLoader> getClassLoadersForTypes(final Class<?>[] types) { + final Map<ClassAndLoader, ClassAndLoader> classesAndLoaders = new LinkedHashMap<>(); + for(final Class<?> c: types) { + final ClassAndLoader cl = new ClassAndLoader(c, true); + if(!classesAndLoaders.containsKey(cl)) { + classesAndLoaders.put(cl, cl); + } + } + return classesAndLoaders.keySet(); + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java new file mode 100644 index 00000000..37786b1e --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java @@ -0,0 +1,882 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.runtime.linker; + +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS; +import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL; +import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; +import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE; +import static jdk.internal.org.objectweb.asm.Opcodes.DUP; +import static jdk.internal.org.objectweb.asm.Opcodes.IFNONNULL; +import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD; +import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE; +import static jdk.internal.org.objectweb.asm.Opcodes.POP; +import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; +import static jdk.nashorn.internal.lookup.Lookup.MH; +import static jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome.ERROR_NO_ACCESSIBLE_CONSTRUCTOR; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Set; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.Type; +import jdk.internal.org.objectweb.asm.commons.InstructionAdapter; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; + +/** + * Generates bytecode for a Java adapter class. Used by the {@link JavaAdapterFactory}. + * </p><p> + * For every protected or public constructor in the extended class, the adapter class will have between one to three + * public constructors (visibility of protected constructors in the extended class is promoted to public). + * <ul> + * <li>In every case, a constructor taking a trailing ScriptObject argument preceded by original constructor arguments + * is always created on the adapter class. When such a constructor is invoked, the passed ScriptObject's member + * functions are used to implement and/or override methods on the original class, dispatched by name. A single + * JavaScript function will act as the implementation for all overloaded methods of the same name. When methods on an + * adapter instance are invoked, the functions are invoked having the ScriptObject passed in the instance constructor as + * their "this". Subsequent changes to the ScriptObject (reassignment or removal of its functions) are not reflected in + * the adapter instance; the method implementations are bound to functions at constructor invocation time. + * {@code java.lang.Object} methods {@code equals}, {@code hashCode}, and {@code toString} can also be overridden. The + * only restriction is that since every JavaScript object already has a {@code toString} function through the + * {@code Object.prototype}, the {@code toString} in the adapter is only overridden if the passed ScriptObject has a + * {@code toString} function as its own property, and not inherited from a prototype. All other adapter methods can be + * implemented or overridden through a prototype-inherited function of the ScriptObject passed to the constructor too. + * </li> + * <li> + * If the original types collectively have only one abstract method, or have several of them, but all share the + * same name, an additional constructor is provided for every original constructor; this one takes a ScriptFunction as + * its last argument preceded by original constructor arguments. This constructor will use the passed function as the + * implementation for all abstract methods. For consistency, any concrete methods sharing the single abstract method + * name will also be overridden by the function. When methods on the adapter instance are invoked, the ScriptFunction is + * invoked with {@code null} as its "this". + * </li> + * <li> + * If the adapter being generated can have class-level overrides, constructors taking same arguments as the superclass + * constructors are also created. These constructors simply delegate to the superclass constructor. They are used to + * create instances of the adapter class with no instance-level overrides. + * </li> + * </ul> + * </p><p> + * For adapter methods that return values, all the JavaScript-to-Java conversions supported by Nashorn will be in effect + * to coerce the JavaScript function return value to the expected Java return type. + * </p><p> + * Since we are adding a trailing argument to the generated constructors in the adapter class, they will never be + * declared as variable arity, even if the original constructor in the superclass was declared as variable arity. The + * reason we are passing the additional argument at the end of the argument list instead at the front is that the + * source-level script expression <code>new X(a, b) { ... }</code> (which is a proprietary syntax extension Nashorn uses + * to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>. + * </p><p> + * It is possible to create two different classes: those that can have both class-level and instance-level overrides, + * and those that can only have instance-level overrides. When + * {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject)} is invoked with non-null {@code classOverrides} + * parameter, an adapter class is created that can have class-level overrides, and the passed script object will be used + * as the implementations for its methods, just as in the above case of the constructor taking a script object. Note + * that in the case of class-level overrides, a new adapter class is created on every invocation, and the implementation + * object is bound to the class, not to any instance. All created instances will share these functions. Of course, when + * instances of such a class are being created, they can still take another object (or possibly a function) in their + * constructor's trailing position and thus provide further instance-specific overrides. The order of invocation is + * always instance-specified method, then a class-specified method, and finally the superclass method. + */ +final class JavaAdapterBytecodeGenerator extends JavaAdapterGeneratorBase { + private static final Type SCRIPT_FUNCTION_TYPE = Type.getType(ScriptFunction.class); + private static final Type STRING_TYPE = Type.getType(String.class); + private static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class); + private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); + private static final String GET_HANDLE_OBJECT_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE, + OBJECT_TYPE, STRING_TYPE, METHOD_TYPE_TYPE); + private static final String GET_HANDLE_FUNCTION_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE, + SCRIPT_FUNCTION_TYPE, METHOD_TYPE_TYPE); + private static final String GET_CLASS_INITIALIZER_DESCRIPTOR = Type.getMethodDescriptor(SCRIPT_OBJECT_TYPE); + private static final Type RUNTIME_EXCEPTION_TYPE = Type.getType(RuntimeException.class); + private static final Type THROWABLE_TYPE = Type.getType(Throwable.class); + private static final Type UNSUPPORTED_OPERATION_TYPE = Type.getType(UnsupportedOperationException.class); + + private static final String SERVICES_CLASS_TYPE_NAME = Type.getInternalName(JavaAdapterServices.class); + private static final String RUNTIME_EXCEPTION_TYPE_NAME = RUNTIME_EXCEPTION_TYPE.getInternalName(); + private static final String ERROR_TYPE_NAME = Type.getInternalName(Error.class); + private static final String THROWABLE_TYPE_NAME = THROWABLE_TYPE.getInternalName(); + private static final String UNSUPPORTED_OPERATION_TYPE_NAME = UNSUPPORTED_OPERATION_TYPE.getInternalName(); + + private static final String METHOD_HANDLE_TYPE_DESCRIPTOR = METHOD_HANDLE_TYPE.getDescriptor(); + private static final String GET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(SCRIPT_OBJECT_TYPE); + private static final String GET_CLASS_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.getType(Class.class)); + + // Package used when the adapter can't be defined in the adaptee's package (either because it's sealed, or because + // it's a java.* package. + private static final String ADAPTER_PACKAGE_PREFIX = "jdk/nashorn/internal/javaadapters/"; + // Class name suffix used to append to the adaptee class name, when it can be defined in the adaptee's package. + private static final String ADAPTER_CLASS_NAME_SUFFIX = "$$NashornJavaAdapter"; + private static final String JAVA_PACKAGE_PREFIX = "java/"; + private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 238; //255 - 17; 17 is the maximum possible length for the global setter inner class suffix + + private static final String CLASS_INIT = "<clinit>"; + private static final String STATIC_GLOBAL_FIELD_NAME = "staticGlobal"; + + /** + * Collection of methods we never override: Object.clone(), Object.finalize(). + */ + private static final Collection<MethodInfo> EXCLUDED = getExcludedMethods(); + + private static final Random random = new SecureRandom(); + + // This is the superclass for our generated adapter. + private final Class<?> superClass; + // Class loader used as the parent for the class loader we'll create to load the generated class. It will be a class + // loader that has the visibility of all original types (class to extend and interfaces to implement) and of the + // Nashorn classes. + private final ClassLoader commonLoader; + // Is this a generator for the version of the class that can have overrides on the class level? + private final boolean classOverride; + // Binary name of the superClass + private final String superClassName; + // Binary name of the generated class. + private final String generatedClassName; + // Binary name of the PrivilegedAction inner class that is used to + private final String globalSetterClassName; + private final Set<String> usedFieldNames = new HashSet<>(); + private final Set<String> abstractMethodNames = new HashSet<>(); + private final String samName; + private final Set<MethodInfo> finalMethods = new HashSet<>(EXCLUDED); + private final Set<MethodInfo> methodInfos = new HashSet<>(); + private boolean autoConvertibleFromFunction = false; + + private final ClassWriter cw; + + /** + * Creates a generator for the bytecode for the adapter for the specified superclass and interfaces. + * @param superClass the superclass the adapter will extend. + * @param interfaces the interfaces the adapter will implement. + * @param commonLoader the class loader that can see all of superClass, interfaces, and Nashorn classes. + * @param classOverride true to generate the bytecode for the adapter that has both class-level and instance-level + * overrides, false to generate the bytecode for the adapter that only has instance-level overrides. + * @throws AdaptationException if the adapter can not be generated for some reason. + */ + JavaAdapterBytecodeGenerator(final Class<?> superClass, final List<Class<?>> interfaces, + final ClassLoader commonLoader, final boolean classOverride) throws AdaptationException { + assert superClass != null && !superClass.isInterface(); + assert interfaces != null; + + this.superClass = superClass; + this.classOverride = classOverride; + this.commonLoader = commonLoader; + cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) { + @Override + protected String getCommonSuperClass(final String type1, final String type2) { + // We need to override ClassWriter.getCommonSuperClass to use this factory's commonLoader as a class + // loader to find the common superclass of two types when needed. + return JavaAdapterBytecodeGenerator.this.getCommonSuperClass(type1, type2); + } + }; + superClassName = Type.getInternalName(superClass); + generatedClassName = getGeneratedClassName(superClass, interfaces); + + // Randomize the name of the privileged global setter, to make it non-feasible to find. + final long l; + synchronized(random) { + l = random.nextLong(); + } + + // NOTE: they way this class name is calculated affects the value of MAX_GENERATED_TYPE_NAME_LENGTH constant. If + // you change the calculation of globalSetterClassName, adjust the constant too. + globalSetterClassName = generatedClassName.concat("$" + Long.toHexString(l & Long.MAX_VALUE)); + cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, generatedClassName, null, superClassName, getInternalTypeNames(interfaces)); + + generateGlobalFields(); + + gatherMethods(superClass); + gatherMethods(interfaces); + samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null; + generateHandleFields(); + if(classOverride) { + generateClassInit(); + } + generateConstructors(); + generateMethods(); + // } + cw.visitEnd(); + } + + private void generateGlobalFields() { + cw.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); + usedFieldNames.add(GLOBAL_FIELD_NAME); + if(classOverride) { + cw.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); + usedFieldNames.add(STATIC_GLOBAL_FIELD_NAME); + } + } + + JavaAdapterClassLoader createAdapterClassLoader() { + return new JavaAdapterClassLoader(generatedClassName, cw.toByteArray(), globalSetterClassName); + } + + boolean isAutoConvertibleFromFunction() { + return autoConvertibleFromFunction; + } + + private static String getGeneratedClassName(final Class<?> superType, final List<Class<?>> interfaces) { + // The class we use to primarily name our adapter is either the superclass, or if it is Object (meaning we're + // just implementing interfaces or extending Object), then the first implemented interface or Object. + final Class<?> namingType = superType == Object.class ? (interfaces.isEmpty()? Object.class : interfaces.get(0)) : superType; + final Package pkg = namingType.getPackage(); + final String namingTypeName = Type.getInternalName(namingType); + final StringBuilder buf = new StringBuilder(); + if (namingTypeName.startsWith(JAVA_PACKAGE_PREFIX) || pkg == null || pkg.isSealed()) { + // Can't define new classes in java.* packages + buf.append(ADAPTER_PACKAGE_PREFIX).append(namingTypeName); + } else { + buf.append(namingTypeName).append(ADAPTER_CLASS_NAME_SUFFIX); + } + final Iterator<Class<?>> it = interfaces.iterator(); + if(superType == Object.class && it.hasNext()) { + it.next(); // Skip first interface, it was used to primarily name the adapter + } + // Append interface names to the adapter name + while(it.hasNext()) { + buf.append("$$").append(it.next().getSimpleName()); + } + return buf.toString().substring(0, Math.min(MAX_GENERATED_TYPE_NAME_LENGTH, buf.length())); + } + + /** + * Given a list of class objects, return an array with their binary names. Used to generate the array of interface + * names to implement. + * @param classes the classes + * @return an array of names + */ + private static String[] getInternalTypeNames(final List<Class<?>> classes) { + final int interfaceCount = classes.size(); + final String[] interfaceNames = new String[interfaceCount]; + for(int i = 0; i < interfaceCount; ++i) { + interfaceNames[i] = Type.getInternalName(classes.get(i)); + } + return interfaceNames; + } + + private void generateHandleFields() { + for (final MethodInfo mi: methodInfos) { + cw.visitField(ACC_PRIVATE | ACC_FINAL, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd(); + if(classOverride) { + cw.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd(); + } + } + } + + private void generateClassInit() { + final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_STATIC, CLASS_INIT, + Type.getMethodDescriptor(Type.VOID_TYPE), null, null)); + + mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getClassOverrides", GET_CLASS_INITIALIZER_DESCRIPTOR); + // Assign MethodHandle fields through invoking getHandle() + for (final MethodInfo mi : methodInfos) { + mv.dup(); + mv.aconst(mi.getName()); + mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString())); + mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_OBJECT_DESCRIPTOR); + mv.putstatic(generatedClassName, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); + } + + // Assign "staticGlobal = Context.getGlobal()" + invokeGetGlobalWithNullCheck(mv); + mv.putstatic(generatedClassName, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + + endInitMethod(mv); + } + + private static void invokeGetGlobalWithNullCheck(final InstructionAdapter mv) { + invokeGetGlobal(mv); + mv.dup(); + mv.invokevirtual(OBJECT_TYPE_NAME, "getClass", GET_CLASS_METHOD_DESCRIPTOR); // check against null Context + mv.pop(); + } + + private void generateConstructors() throws AdaptationException { + boolean gotCtor = false; + for (final Constructor<?> ctor: superClass.getDeclaredConstructors()) { + final int modifier = ctor.getModifiers(); + if((modifier & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) { + generateConstructors(ctor); + gotCtor = true; + } + } + if(!gotCtor) { + throw new AdaptationException(ERROR_NO_ACCESSIBLE_CONSTRUCTOR, superClass.getCanonicalName()); + } + } + + private void generateConstructors(final Constructor<?> ctor) { + if(classOverride) { + // Generate a constructor that just delegates to ctor. This is used with class-level overrides, when we want + // to create instances without further per-instance overrides. + generateDelegatingConstructor(ctor); + } + + // Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the + // beginning of its parameter list. + generateOverridingConstructor(ctor, false); + + if (samName != null) { + if (!autoConvertibleFromFunction && ctor.getParameterTypes().length == 0) { + // If the original type only has a single abstract method name, as well as a default ctor, then it can + // be automatically converted from JS function. + autoConvertibleFromFunction = true; + } + // If all our abstract methods have a single name, generate an additional constructor, one that takes a + // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods. + generateOverridingConstructor(ctor, true); + } + } + + private void generateDelegatingConstructor(final Constructor<?> ctor) { + final Type originalCtorType = Type.getType(ctor); + final Type[] argTypes = originalCtorType.getArgumentTypes(); + + // All constructors must be public, even if in the superclass they were protected. + final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, INIT, + Type.getMethodDescriptor(originalCtorType.getReturnType(), argTypes), null, null)); + + mv.visitCode(); + // Invoke super constructor with the same arguments. + mv.visitVarInsn(ALOAD, 0); + int offset = 1; // First arg is at position 1, after this. + for (Type argType: argTypes) { + mv.load(offset, argType); + offset += argType.getSize(); + } + mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor()); + + endInitMethod(mv); + } + + /** + * Generates a constructor for the adapter class. This constructor will take the same arguments as the supertype + * constructor passed as the argument here, and delegate to it. However, it will take an additional argument of + * either ScriptObject or ScriptFunction type (based on the value of the "fromFunction" parameter), and initialize + * all the method handle fields of the adapter instance with functions from the script object (or the script + * function itself, if that's what's passed). There is one method handle field in the adapter class for every method + * that can be implemented or overridden; the name of every field is same as the name of the method, with a number + * suffix that makes it unique in case of overloaded methods. The generated constructor will invoke + * {@link #getHandle(ScriptFunction, MethodType, boolean)} or {@link #getHandle(Object, String, MethodType, + * boolean)} to obtain the method handles; these methods make sure to add the necessary conversions and arity + * adjustments so that the resulting method handles can be invoked from generated methods using {@code invokeExact}. + * The constructor that takes a script function will only initialize the methods with the same name as the single + * abstract method. The constructor will also store the Nashorn global that was current at the constructor + * invocation time in a field named "global". The generated constructor will be public, regardless of whether the + * supertype constructor was public or protected. The generated constructor will not be variable arity, even if the + * supertype constructor was. + * @param ctor the supertype constructor that is serving as the base for the generated constructor. + * @param fromFunction true if we're generating a constructor that initializes SAM types from a single + * ScriptFunction passed to it, false if we're generating a constructor that initializes an arbitrary type from a + * ScriptObject passed to it. + */ + private void generateOverridingConstructor(final Constructor<?> ctor, final boolean fromFunction) { + final Type originalCtorType = Type.getType(ctor); + final Type[] originalArgTypes = originalCtorType.getArgumentTypes(); + final int argLen = originalArgTypes.length; + final Type[] newArgTypes = new Type[argLen + 1]; + + // Insert ScriptFunction|Object as the last argument to the constructor + final Type extraArgumentType = fromFunction ? SCRIPT_FUNCTION_TYPE : OBJECT_TYPE; + newArgTypes[argLen] = extraArgumentType; + System.arraycopy(originalArgTypes, 0, newArgTypes, 0, argLen); + + // All constructors must be public, even if in the superclass they were protected. + // Existing super constructor <init>(this, args...) triggers generating <init>(this, scriptObj, args...). + final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, INIT, + Type.getMethodDescriptor(originalCtorType.getReturnType(), newArgTypes), null, null)); + + mv.visitCode(); + // First, invoke super constructor with original arguments. If the form of the constructor we're generating is + // <init>(this, args..., scriptFn), then we're invoking super.<init>(this, args...). + mv.visitVarInsn(ALOAD, 0); + final Class<?>[] argTypes = ctor.getParameterTypes(); + int offset = 1; // First arg is at position 1, after this. + for (int i = 0; i < argLen; ++i) { + final Type argType = Type.getType(argTypes[i]); + mv.load(offset, argType); + offset += argType.getSize(); + } + mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor()); + + // Get a descriptor to the appropriate "JavaAdapterFactory.getHandle" method. + final String getHandleDescriptor = fromFunction ? GET_HANDLE_FUNCTION_DESCRIPTOR : GET_HANDLE_OBJECT_DESCRIPTOR; + + // Assign MethodHandle fields through invoking getHandle() + for (final MethodInfo mi : methodInfos) { + mv.visitVarInsn(ALOAD, 0); + if (fromFunction && !mi.getName().equals(samName)) { + // Constructors initializing from a ScriptFunction only initialize methods with the SAM name. + // NOTE: if there's a concrete overloaded method sharing the SAM name, it'll be overriden too. This + // is a deliberate design choice. All other method handles are initialized to null. + mv.visitInsn(ACONST_NULL); + } else { + mv.visitVarInsn(ALOAD, offset); + if(!fromFunction) { + mv.aconst(mi.getName()); + } + mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString())); + mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor); + } + mv.putfield(generatedClassName, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); + } + + // Assign "this.global = Context.getGlobal()" + mv.visitVarInsn(ALOAD, 0); + invokeGetGlobalWithNullCheck(mv); + mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + + endInitMethod(mv); + } + + private static void endInitMethod(final InstructionAdapter mv) { + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + private static void invokeGetGlobal(final InstructionAdapter mv) { + mv.invokestatic(CONTEXT_TYPE_NAME, "getGlobal", GET_GLOBAL_METHOD_DESCRIPTOR); + } + + private void invokeSetGlobal(final InstructionAdapter mv) { + mv.invokestatic(globalSetterClassName, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR); + } + + /** + * Encapsulation of the information used to generate methods in the adapter classes. Basically, a wrapper around the + * reflective Method object, a cached MethodType, and the name of the field in the adapter class that will hold the + * method handle serving as the implementation of this method in adapter instances. + * + */ + private static class MethodInfo { + private final Method method; + private final MethodType type; + private String methodHandleInstanceFieldName; + private String methodHandleClassFieldName; + + private MethodInfo(final Class<?> clazz, final String name, final Class<?>... argTypes) throws NoSuchMethodException { + this(clazz.getDeclaredMethod(name, argTypes)); + } + + private MethodInfo(final Method method) { + this.method = method; + this.type = MH.type(method.getReturnType(), method.getParameterTypes()); + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof MethodInfo && equals((MethodInfo)obj); + } + + private boolean equals(final MethodInfo other) { + // Only method name and type are used for comparison; method handle field name is not. + return getName().equals(other.getName()) && type.equals(other.type); + } + + String getName() { + return method.getName(); + } + + @Override + public int hashCode() { + return getName().hashCode() ^ type.hashCode(); + } + + void setIsCanonical(final Set<String> usedFieldNames, boolean classOverride) { + methodHandleInstanceFieldName = nextName(usedFieldNames); + if(classOverride) { + methodHandleClassFieldName = nextName(usedFieldNames); + } + } + + String nextName(final Set<String> usedFieldNames) { + int i = 0; + final String name = getName(); + String nextName = name; + while (!usedFieldNames.add(nextName)) { + final String ordinal = String.valueOf(i++); + final int maxNameLen = 255 - ordinal.length(); + nextName = (name.length() <= maxNameLen ? name : name.substring(0, maxNameLen)).concat(ordinal); + } + return nextName; + } + + } + + private void generateMethods() { + for(final MethodInfo mi: methodInfos) { + generateMethod(mi); + } + } + + /** + * Generates a method in the adapter class that adapts a method from the original class. The generated methods will + * inspect the method handle field assigned to them. If it is null (the JS object doesn't provide an implementation + * for the method) then it will either invoke its version in the supertype, or if it is abstract, throw an + * {@link UnsupportedOperationException}. Otherwise, if the method handle field's value is not null, the handle is + * invoked using invokeExact (signature polymorphic invocation as per JLS 15.12.3). Before the invocation, the + * current Nashorn {@link Context} is checked, and if it is different than the global used to create the adapter + * instance, the creating global is set to be the current global. In this case, the previously current global is + * restored after the invocation. If invokeExact results in a Throwable that is not one of the method's declared + * exceptions, and is not an unchecked throwable, then it is wrapped into a {@link RuntimeException} and the runtime + * exception is thrown. The method handle retrieved from the field is guaranteed to exactly match the signature of + * the method; this is guaranteed by the way constructors of the adapter class obtain them using + * {@link #getHandle(Object, String, MethodType, boolean)}. + * @param mi the method info describing the method to be generated. + */ + private void generateMethod(final MethodInfo mi) { + final Method method = mi.method; + final int mod = method.getModifiers(); + final int access = ACC_PUBLIC | (method.isVarArgs() ? ACC_VARARGS : 0); + final Class<?>[] exceptions = method.getExceptionTypes(); + final String[] exceptionNames = new String[exceptions.length]; + for (int i = 0; i < exceptions.length; ++i) { + exceptionNames[i] = Type.getInternalName(exceptions[i]); + } + final MethodType type = mi.type; + final String methodDesc = type.toMethodDescriptorString(); + final String name = mi.getName(); + + final Type asmType = Type.getMethodType(methodDesc); + final Type[] asmArgTypes = asmType.getArgumentTypes(); + + // Determine the first index for a local variable + int nextLocalVar = 1; // this + for(final Type t: asmArgTypes) { + nextLocalVar += t.getSize(); + } + + final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(access, name, methodDesc, null, + exceptionNames)); + mv.visitCode(); + + final Label instanceHandleDefined = new Label(); + final Label classHandleDefined = new Label(); + + final Type asmReturnType = Type.getType(type.returnType()); + + // See if we have instance handle defined + mv.visitVarInsn(ALOAD, 0); + mv.getfield(generatedClassName, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); + // stack: [instanceHandle] + jumpIfNonNullKeepOperand(mv, instanceHandleDefined); + + if(classOverride) { + // See if we have the static handle + mv.getstatic(generatedClassName, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); + // stack: [classHandle] + jumpIfNonNullKeepOperand(mv, classHandleDefined); + } + + // No handle is available, fall back to default behavior + if(Modifier.isAbstract(mod)) { + // If the super method is abstract, throw an exception + mv.anew(UNSUPPORTED_OPERATION_TYPE); + mv.dup(); + mv.invokespecial(UNSUPPORTED_OPERATION_TYPE_NAME, INIT, VOID_NOARG_METHOD_DESCRIPTOR); + mv.athrow(); + } else { + // If the super method is not abstract, delegate to it. + mv.visitVarInsn(ALOAD, 0); + int nextParam = 1; + for(final Type t: asmArgTypes) { + mv.load(nextParam, t); + nextParam += t.getSize(); + } + mv.invokespecial(superClassName, name, methodDesc); + mv.areturn(asmReturnType); + } + + final Label setupGlobal = new Label(); + + if(classOverride) { + mv.visitLabel(classHandleDefined); + // If class handle is defined, load the static defining global + mv.getstatic(generatedClassName, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + // stack: [creatingGlobal := classGlobal, classHandle] + mv.goTo(setupGlobal); + } + + mv.visitLabel(instanceHandleDefined); + // If instance handle is defined, load the instance defining global + mv.visitVarInsn(ALOAD, 0); + mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + // stack: [creatingGlobal := instanceGlobal, instanceHandle] + + // fallthrough to setupGlobal + + // stack: [creatingGlobal, someHandle] + mv.visitLabel(setupGlobal); + + final int currentGlobalVar = nextLocalVar++; + final int globalsDifferVar = nextLocalVar++; + + mv.dup(); + // stack: [creatingGlobal, creatingGlobal, someHandle] + + // Emit code for switching to the creating global + // ScriptObject currentGlobal = Context.getGlobal(); + invokeGetGlobal(mv); + mv.dup(); + mv.visitVarInsn(ASTORE, currentGlobalVar); + // stack: [currentGlobal, creatingGlobal, creatingGlobal, someHandle] + // if(definingGlobal == currentGlobal) { + final Label globalsDiffer = new Label(); + mv.ifacmpne(globalsDiffer); + // stack: [someGlobal, someHandle] + // globalsDiffer = false + mv.pop(); + // stack: [someHandle] + mv.iconst(0); // false + // stack: [false, someHandle] + final Label invokeHandle = new Label(); + mv.goTo(invokeHandle); + mv.visitLabel(globalsDiffer); + // } else { + // Context.setGlobal(definingGlobal); + // stack: [someGlobal, someHandle] + invokeSetGlobal(mv); + // stack: [someHandle] + // globalsDiffer = true + mv.iconst(1); + // stack: [true, someHandle] + + mv.visitLabel(invokeHandle); + mv.visitVarInsn(ISTORE, globalsDifferVar); + // stack: [someHandle] + + // Load all parameters back on stack for dynamic invocation. + int varOffset = 1; + for (final Type t : asmArgTypes) { + mv.load(varOffset, t); + varOffset += t.getSize(); + } + + // Invoke the target method handle + final Label tryBlockStart = new Label(); + mv.visitLabel(tryBlockStart); + mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString()); + final Label tryBlockEnd = new Label(); + mv.visitLabel(tryBlockEnd); + emitFinally(mv, currentGlobalVar, globalsDifferVar); + mv.areturn(asmReturnType); + + // If Throwable is not declared, we need an adapter from Throwable to RuntimeException + final boolean throwableDeclared = isThrowableDeclared(exceptions); + final Label throwableHandler; + if (!throwableDeclared) { + // Add "throw new RuntimeException(Throwable)" handler for Throwable + throwableHandler = new Label(); + mv.visitLabel(throwableHandler); + mv.anew(RUNTIME_EXCEPTION_TYPE); + mv.dupX1(); + mv.swap(); + mv.invokespecial(RUNTIME_EXCEPTION_TYPE_NAME, INIT, Type.getMethodDescriptor(Type.VOID_TYPE, THROWABLE_TYPE)); + // Fall through to rethrow handler + } else { + throwableHandler = null; + } + final Label rethrowHandler = new Label(); + mv.visitLabel(rethrowHandler); + // Rethrow handler for RuntimeException, Error, and all declared exception types + emitFinally(mv, currentGlobalVar, globalsDifferVar); + mv.athrow(); + final Label methodEnd = new Label(); + mv.visitLabel(methodEnd); + + mv.visitLocalVariable("currentGlobal", SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, setupGlobal, methodEnd, currentGlobalVar); + mv.visitLocalVariable("globalsDiffer", Type.INT_TYPE.getDescriptor(), null, setupGlobal, methodEnd, globalsDifferVar); + + if(throwableDeclared) { + mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, THROWABLE_TYPE_NAME); + assert throwableHandler == null; + } else { + mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, RUNTIME_EXCEPTION_TYPE_NAME); + mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, ERROR_TYPE_NAME); + for(final String excName: exceptionNames) { + mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, excName); + } + mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, throwableHandler, THROWABLE_TYPE_NAME); + } + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + /** + * Emits code for jumping to a label if the top stack operand is not null. The operand is kept on the stack if it + * is not null (so is available to code at the jump address) and is popped if it is null. + * @param mv the instruction adapter being used to emit code + * @param label the label to jump to + */ + private static void jumpIfNonNullKeepOperand(final InstructionAdapter mv, final Label label) { + mv.visitInsn(DUP); + mv.visitJumpInsn(IFNONNULL, label); + mv.visitInsn(POP); + } + + /** + * Emit code to restore the previous Nashorn Context when needed. + * @param mv the instruction adapter + * @param currentGlobalVar index of the local variable holding the reference to the current global at method + * entry. + * @param globalsDifferVar index of the boolean local variable that is true if the global needs to be restored. + */ + private void emitFinally(final InstructionAdapter mv, final int currentGlobalVar, final int globalsDifferVar) { + // Emit code to restore the previous Nashorn global if needed + mv.visitVarInsn(ILOAD, globalsDifferVar); + final Label skip = new Label(); + mv.ifeq(skip); + mv.visitVarInsn(ALOAD, currentGlobalVar); + invokeSetGlobal(mv); + mv.visitLabel(skip); + } + + private static boolean isThrowableDeclared(final Class<?>[] exceptions) { + for (final Class<?> exception : exceptions) { + if (exception == Throwable.class) { + return true; + } + } + return false; + } + + /** + * Gathers methods that can be implemented or overridden from the specified type into this factory's + * {@link #methodInfos} set. It will add all non-final, non-static methods that are either public or protected from + * the type if the type itself is public. If the type is a class, the method will recursively invoke itself for its + * superclass and the interfaces it implements, and add further methods that were not directly declared on the + * class. + * @param type the type defining the methods. + */ + private void gatherMethods(final Class<?> type) { + if (Modifier.isPublic(type.getModifiers())) { + final Method[] typeMethods = type.isInterface() ? type.getMethods() : type.getDeclaredMethods(); + + for (final Method typeMethod: typeMethods) { + final int m = typeMethod.getModifiers(); + if (Modifier.isStatic(m)) { + continue; + } + if (Modifier.isPublic(m) || Modifier.isProtected(m)) { + final MethodInfo mi = new MethodInfo(typeMethod); + if (Modifier.isFinal(m)) { + finalMethods.add(mi); + } else if (!finalMethods.contains(mi) && methodInfos.add(mi)) { + if (Modifier.isAbstract(m)) { + abstractMethodNames.add(mi.getName()); + } + mi.setIsCanonical(usedFieldNames, classOverride); + } + } + } + } + // If the type is a class, visit its superclasses and declared interfaces. If it's an interface, we're done. + // Needing to invoke the method recursively for a non-interface Class object is the consequence of needing to + // see all declared protected methods, and Class.getDeclaredMethods() doesn't provide those declared in a + // superclass. For interfaces, we used Class.getMethods(), as we're only interested in public ones there, and + // getMethods() does provide those declared in a superinterface. + if (!type.isInterface()) { + final Class<?> superType = type.getSuperclass(); + if (superType != null) { + gatherMethods(superType); + } + for (final Class<?> itf: type.getInterfaces()) { + gatherMethods(itf); + } + } + } + + private void gatherMethods(final List<Class<?>> classes) { + for(final Class<?> c: classes) { + gatherMethods(c); + } + } + + /** + * Creates a collection of methods that are not final, but we still never allow them to be overridden in adapters, + * as explicitly declaring them automatically is a bad idea. Currently, this means {@code Object.finalize()} and + * {@code Object.clone()}. + * @return a collection of method infos representing those methods that we never override in adapter classes. + */ + private static Collection<MethodInfo> getExcludedMethods() { + return AccessController.doPrivileged(new PrivilegedAction<Collection<MethodInfo>>() { + @Override + public Collection<MethodInfo> run() { + try { + return Arrays.asList( + new MethodInfo(Object.class, "finalize"), + new MethodInfo(Object.class, "clone")); + } catch (final NoSuchMethodException e) { + throw new AssertionError(e); + } + } + }); + } + + private String getCommonSuperClass(final String type1, final String type2) { + try { + final Class<?> c1 = Class.forName(type1.replace('/', '.'), false, commonLoader); + final Class<?> c2 = Class.forName(type2.replace('/', '.'), false, commonLoader); + if (c1.isAssignableFrom(c2)) { + return type1; + } + if (c2.isAssignableFrom(c1)) { + return type2; + } + if (c1.isInterface() || c2.isInterface()) { + return OBJECT_TYPE_NAME; + } + return assignableSuperClass(c1, c2).getName().replace('.', '/'); + } catch(final ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + private static Class<?> assignableSuperClass(final Class<?> c1, final Class<?> c2) { + final Class<?> superClass = c1.getSuperclass(); + return superClass.isAssignableFrom(c2) ? superClass : assignableSuperClass(superClass, c2); + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java new file mode 100644 index 00000000..c791274e --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java @@ -0,0 +1,225 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.runtime.linker; + +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; +import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL; +import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; +import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN; +import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; + +import java.security.AccessController; +import java.security.AllPermission; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.security.Permissions; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.security.SecureClassLoader; +import jdk.internal.dynalink.beans.StaticClass; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.Type; +import jdk.internal.org.objectweb.asm.commons.InstructionAdapter; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptObject; + +/** + * This class encapsulates the bytecode of the adapter class and can be used to load it into the JVM as an actual Class. + * It can be invoked repeatedly to create multiple adapter classes from the same bytecode; adapter classes that have + * class-level overrides must be re-created for every set of such overrides. Note that while this class is named + * "class loader", it does not, in fact, extend {@code ClassLoader}, but rather uses them internally. Instances of this + * class are normally created by {@link JavaAdapterBytecodeGenerator}. + */ +class JavaAdapterClassLoader extends JavaAdapterGeneratorBase { + private static final Type PRIVILEGED_ACTION_TYPE = Type.getType(PrivilegedAction.class); + + private static final String PRIVILEGED_ACTION_TYPE_NAME = PRIVILEGED_ACTION_TYPE.getInternalName(); + private static final String PRIVILEGED_RUN_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE); + + private static final ProtectionDomain GENERATED_PROTECTION_DOMAIN = createGeneratedProtectionDomain(); + + private final String className; + private final byte[] classBytes; + private final String globalSetterClassName; + + JavaAdapterClassLoader(String className, byte[] classBytes, String globalSetterClassName) { + this.className = className.replace('/', '.'); + this.classBytes = classBytes; + this.globalSetterClassName = globalSetterClassName.replace('/', '.'); + } + + /** + * Loads the generated adapter class into the JVM. + * @param parentLoader the parent class loader for the generated class loader + * @return the generated adapter class + */ + StaticClass generateClass(final ClassLoader parentLoader) { + return AccessController.doPrivileged(new PrivilegedAction<StaticClass>() { + @Override + public StaticClass run() { + try { + return StaticClass.forClass(Class.forName(className, true, createClassLoader(parentLoader))); + } catch (final ClassNotFoundException e) { + throw new AssertionError(e); // cannot happen + } + } + }); + } + + private static class AdapterLoader extends SecureClassLoader { + AdapterLoader(ClassLoader parent) { + super(parent); + } + } + + static boolean isAdapterClass(Class<?> clazz) { + return clazz.getClassLoader() instanceof AdapterLoader; + } + + // Note that the adapter class is created in the protection domain of the class/interface being + // extended/implemented, and only the privileged global setter action class is generated in the protection domain + // of Nashorn itself. Also note that the creation and loading of the global setter is deferred until it is + // required by JVM linker, which will only happen on first invocation of any of the adapted method. We could defer + // it even more by separating its invocation into a separate static method on the adapter class, but then someone + // with ability to introspect on the class and use setAccessible(true) on it could invoke the method. It's a + // security tradeoff... + private ClassLoader createClassLoader(final ClassLoader parentLoader) { + return new AdapterLoader(parentLoader) { + private final ClassLoader myLoader = getClass().getClassLoader(); + private final ProtectionDomain myProtectionDomain = getClass().getProtectionDomain(); + + @Override + public Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException { + try { + return super.loadClass(name, resolve); + } catch (final SecurityException se) { + // we may be implementing an interface or extending a class that was + // loaded by a loader that prevents package.access. If so, it'd throw + // SecurityException for nashorn's classes!. For adapter's to work, we + // should be able to refer to nashorn classes. + if (name.startsWith("jdk.nashorn.internal.")) { + return myLoader.loadClass(name); + } + throw se; + } + } + + @Override + protected Class<?> findClass(final String name) throws ClassNotFoundException { + if(name.equals(className)) { + return defineClass(name, classBytes, 0, classBytes.length, GENERATED_PROTECTION_DOMAIN); + } else if(name.equals(globalSetterClassName)) { + final byte[] bytes = generatePrivilegedActionClassBytes(globalSetterClassName.replace('.', '/')); + return defineClass(name, bytes, 0, bytes.length, myProtectionDomain); + } else { + throw new ClassNotFoundException(name); + } + } + }; + } + + private static ProtectionDomain createGeneratedProtectionDomain() { + // Generated classes need to have AllPermission. Since we require the "createClassLoader" RuntimePermission, we + // can create a class loader that'll load new classes with any permissions. Our generated classes are just + // delegating adapters, so having AllPermission can't cause anything wrong; the effective set of permissions for + // the executing script functions will still be limited by the permissions of the caller and the permissions of + // the script. + final Permissions permissions = new Permissions(); + permissions.add(new AllPermission()); + return new ProtectionDomain(new CodeSource(null, (CodeSigner[])null), permissions); + } + + /** + * Generates a PrivilegedAction implementation class for invoking {@link Context#setGlobal(ScriptObject)} from the + * adapter class. + */ + private static byte[] generatePrivilegedActionClassBytes(final String className) { + final ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + // class GlobalSetter implements PrivilegedAction { + w.visit(Opcodes.V1_7, ACC_SUPER | ACC_FINAL, className, null, OBJECT_TYPE_NAME, new String[] { + PRIVILEGED_ACTION_TYPE_NAME + }); + + // private final ScriptObject global; + w.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); + + // private GlobalSetter(ScriptObject global) { + InstructionAdapter mv = new InstructionAdapter(w.visitMethod(ACC_PRIVATE, INIT, + SET_GLOBAL_METHOD_DESCRIPTOR, null, new String[0])); + mv.visitCode(); + // super(); + mv.visitVarInsn(ALOAD, 0); + mv.invokespecial(OBJECT_TYPE_NAME, INIT, VOID_NOARG_METHOD_DESCRIPTOR); + // this.global = global; + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.putfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + + mv.visitInsn(RETURN); + mv.visitEnd(); + mv.visitMaxs(0, 0); + + // public Object run() { + mv = new InstructionAdapter(w.visitMethod(ACC_PUBLIC, "run", PRIVILEGED_RUN_METHOD_DESCRIPTOR, null, + new String[0])); + mv.visitCode(); + // Context.setGlobal(this.global); + mv.visitVarInsn(ALOAD, 0); + mv.getfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + mv.invokestatic(CONTEXT_TYPE_NAME, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR); + // return null; + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + + mv.visitEnd(); + mv.visitMaxs(0, 0); + + // static void setGlobal(ScriptObject global) { + mv = new InstructionAdapter(w.visitMethod(ACC_STATIC, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR, null, + new String[0])); + mv.visitCode(); + // new GlobalSetter(ScriptObject global) + mv.anew(Type.getType("L" + className + ";")); + mv.dup(); + mv.visitVarInsn(ALOAD, 0); + mv.invokespecial(className, INIT, SET_GLOBAL_METHOD_DESCRIPTOR); + // AccessController.doPrivileged(...) + mv.invokestatic(Type.getInternalName(AccessController.class), "doPrivileged", Type.getMethodDescriptor( + OBJECT_TYPE, PRIVILEGED_ACTION_TYPE)); + mv.pop(); + mv.visitInsn(RETURN); + + mv.visitEnd(); + mv.visitMaxs(0, 0); + + return w.toByteArray(); + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java index 7b0d6d76..f3f51fd8 100644 --- a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java @@ -25,110 +25,39 @@ package jdk.nashorn.internal.runtime.linker; -import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL; -import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE; -import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; -import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; -import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; -import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS; -import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL; -import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; -import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN; -import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE; -import static jdk.internal.org.objectweb.asm.Opcodes.DUP; -import static jdk.internal.org.objectweb.asm.Opcodes.IFNONNULL; -import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD; -import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE; -import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; -import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.lookup.Lookup.MH; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; -import java.security.AllPermission; -import java.security.CodeSigner; -import java.security.CodeSource; -import java.security.Permissions; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; -import java.security.ProtectionDomain; -import java.security.SecureClassLoader; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Random; -import java.util.Set; import jdk.internal.dynalink.beans.StaticClass; import jdk.internal.dynalink.support.LinkRequestImpl; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.Label; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; -import jdk.internal.org.objectweb.asm.commons.InstructionAdapter; import jdk.nashorn.internal.objects.NativeJava; -import jdk.nashorn.internal.runtime.Context; -import jdk.nashorn.internal.runtime.ECMAErrors; import jdk.nashorn.internal.runtime.ECMAException; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; -import jdk.nashorn.internal.runtime.ScriptRuntime; -import jdk.nashorn.internal.runtime.Undefined; /** * <p>A factory class that generates adapter classes. Adapter classes allow implementation of Java interfaces and * extending of Java classes from JavaScript. For every combination of a superclass to extend and interfaces to * implement (collectively: "original types"), exactly one adapter class is generated that extends the specified - * superclass and implements the specified interfaces. + * superclass and implements the specified interfaces. (But see the discussion of class-based overrides for exceptions.) * </p><p> * The adapter class is generated in a new secure class loader that inherits Nashorn's protection domain, and has either * one of the original types' class loader or the Nashorn's class loader as its parent - the parent class loader * is chosen so that all the original types and the Nashorn core classes are visible from it (as the adapter will have * constant pool references to ScriptObject and ScriptFunction classes). In case none of the candidate class loaders has - * visibility of all the required types, an error is thrown. - * </p><p> - * For every protected or public constructor in the extended class, the adapter class will have one or two public - * constructors (visibility of protected constructors in the extended class is promoted to public). In every case, for - * every original constructor, a new constructor taking a trailing ScriptObject argument preceded by original - * constructor arguments is present on the adapter class. When such a constructor is invoked, the passed ScriptObject's - * member functions are used to implement and/or override methods on the original class, dispatched by name. A single - * JavaScript function will act as the implementation for all overloaded methods of the same name. When methods on an - * adapter instance are invoked, the functions are invoked having the ScriptObject passed in the instance constructor as - * their "this". Subsequent changes to the ScriptObject (reassignment or removal of its functions) are not reflected in - * the adapter instance; the method implementations are bound to functions at constructor invocation time. - * {@code java.lang.Object} methods {@code equals}, {@code hashCode}, and {@code toString} can also be overridden. The - * only restriction is that since every JavaScript object already has a {@code toString} function through the - * {@code Object.prototype}, the {@code toString} in the adapter is only overridden if the passed ScriptObject has a - * {@code toString} function as its own property, and not inherited from a prototype. All other adapter methods can be - * implemented or overridden through a prototype-inherited function of the ScriptObject passed to the constructor too. - * </p><p> - * If the original types collectively have only one abstract method, or have several of them, but all share the - * same name, an additional constructor is provided for every original constructor; this one takes a ScriptFunction as - * its last argument preceded by original constructor arguments. This constructor will use the passed function as the - * implementation for all abstract methods. For consistency, any concrete methods sharing the single abstract method - * name will also be overridden by the function. When methods on the adapter instance are invoked, the ScriptFunction is - * invoked with {@code null} as its "this". - * </p><p> - * For adapter methods that return values, all the JavaScript-to-Java conversions supported by Nashorn will be in effect - * to coerce the JavaScript function return value to the expected Java return type. - * </p><p> - * Since we are adding a trailing argument to the generated constructors in the adapter class, they will never be - * declared as variable arity, even if the original constructor in the superclass was declared as variable arity. The - * reason we are passing the additional argument at the end of the argument list instead at the front is that the - * source-level script expression <code>new X(a, b) { ... }</code> (which is a proprietary syntax extension Nashorn uses - * to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>. + * visibility of all the required types, an error is thrown. The class uses {@link JavaAdapterBytecodeGenerator} to + * generate the adapter class itself; see its documentation for details about the generated class. * </p><p> * You normally don't use this class directly, but rather either create adapters from script using * {@link NativeJava#extend(Object, Object...)}, using the {@code new} operator on abstract classes and interfaces (see @@ -138,72 +67,6 @@ import jdk.nashorn.internal.runtime.Undefined; */ public final class JavaAdapterFactory { - private static final Type SCRIPT_FUNCTION_TYPE = Type.getType(ScriptFunction.class); - private static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class); - private static final Type OBJECT_TYPE = Type.getType(Object.class); - private static final Type STRING_TYPE = Type.getType(String.class); - private static final Type CONTEXT_TYPE = Type.getType(Context.class); - private static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class); - private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); - private static final String GET_HANDLE_OBJECT_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE, - OBJECT_TYPE, STRING_TYPE, METHOD_TYPE_TYPE, Type.BOOLEAN_TYPE); - private static final String GET_HANDLE_FUNCTION_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE, - SCRIPT_FUNCTION_TYPE, METHOD_TYPE_TYPE, Type.BOOLEAN_TYPE); - private static final Type RUNTIME_EXCEPTION_TYPE = Type.getType(RuntimeException.class); - private static final Type THROWABLE_TYPE = Type.getType(Throwable.class); - private static final Type PRIVILEGED_ACTION_TYPE = Type.getType(PrivilegedAction.class); - private static final Type UNSUPPORTED_OPERATION_TYPE = Type.getType(UnsupportedOperationException.class); - - private static final String THIS_CLASS_TYPE_NAME = Type.getInternalName(JavaAdapterFactory.class); - private static final String RUNTIME_EXCEPTION_TYPE_NAME = RUNTIME_EXCEPTION_TYPE.getInternalName(); - private static final String ERROR_TYPE_NAME = Type.getInternalName(Error.class); - private static final String THROWABLE_TYPE_NAME = THROWABLE_TYPE.getInternalName(); - private static final String CONTEXT_TYPE_NAME = CONTEXT_TYPE.getInternalName(); - private static final String OBJECT_TYPE_NAME = OBJECT_TYPE.getInternalName(); - private static final String PRIVILEGED_ACTION_TYPE_NAME = PRIVILEGED_ACTION_TYPE.getInternalName(); - private static final String UNSUPPORTED_OPERATION_TYPE_NAME = UNSUPPORTED_OPERATION_TYPE.getInternalName(); - - private static final String METHOD_HANDLE_TYPE_DESCRIPTOR = METHOD_HANDLE_TYPE.getDescriptor(); - private static final String SCRIPT_OBJECT_TYPE_DESCRIPTOR = SCRIPT_OBJECT_TYPE.getDescriptor(); - private static final String GET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(SCRIPT_OBJECT_TYPE); - private static final String SET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, SCRIPT_OBJECT_TYPE); - private static final String GET_CLASS_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.getType(Class.class)); - private static final String PRIVILEGED_RUN_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE); - - // Package used when the adapter can't be defined in the adaptee's package (either because it's sealed, or because - // it's a java.* package. - private static final String ADAPTER_PACKAGE_PREFIX = "jdk/nashorn/internal/javaadapters/"; - // Class name suffix used to append to the adaptee class name, when it can be defined in the adaptee's package. - private static final String ADAPTER_CLASS_NAME_SUFFIX = "$$NashornJavaAdapter"; - private static final String JAVA_PACKAGE_PREFIX = "java/"; - private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 238; //255 - 17; 17 is the maximum possible length for the global setter inner class suffix - - private static final String INIT = "<init>"; - private static final String VOID_NOARG = Type.getMethodDescriptor(Type.VOID_TYPE); - private static final String GLOBAL_FIELD_NAME = "global"; - - /** - * Contains various outcomes for attempting to generate an adapter class. These are stored in AdapterInfo instances. - * We have a successful outcome (adapter class was generated) and four possible error outcomes: superclass is final, - * superclass is not public, superclass has no public or protected constructor, more than one superclass was - * specified. We don't throw exceptions when we try to generate the adapter, but rather just record these error - * conditions as they are still useful as partial outcomes, as Nashorn's linker can still successfully check whether - * the class can be autoconverted from a script function even when it is not possible to generate an adapter for it. - */ - private enum AdaptationOutcome { - SUCCESS, - ERROR_FINAL_CLASS, - ERROR_NON_PUBLIC_CLASS, - ERROR_NO_ACCESSIBLE_CONSTRUCTOR, - ERROR_MULTIPLE_SUPERCLASSES, - ERROR_NO_COMMON_LOADER - } - - /** - * Collection of methods we never override: Object.clone(), Object.finalize(). - */ - private static final Collection<MethodInfo> EXCLUDED = getExcludedMethods(); - /** * A mapping from an original Class object to AdapterInfo representing the adapter for the class it represents. */ @@ -214,127 +77,6 @@ public final class JavaAdapterFactory { } }; - private static final Random random = new SecureRandom(); - private static final ProtectionDomain GENERATED_PROTECTION_DOMAIN = createGeneratedProtectionDomain(); - - // This is the superclass for our generated adapter. - private final Class<?> superClass; - // Class loader used as the parent for the class loader we'll create to load the generated class. It will be a class - // loader that has the visibility of all original types (class to extend and interfaces to implement) and of the - // Nashorn classes. - private final ClassLoader commonLoader; - - // Binary name of the superClass - private final String superClassName; - // Binary name of the generated class. - private final String generatedClassName; - // Binary name of the PrivilegedAction inner class that is used to - private final String globalSetterClassName; - private final Set<String> usedFieldNames = new HashSet<>(); - private final Set<String> abstractMethodNames = new HashSet<>(); - private final String samName; - private final Set<MethodInfo> finalMethods = new HashSet<>(EXCLUDED); - private final Set<MethodInfo> methodInfos = new HashSet<>(); - private boolean autoConvertibleFromFunction = false; - - private final ClassWriter cw; - - /** - * Creates a factory that will produce the adapter type for the specified original type. - * @param originalType the type for which this factory will generate the adapter type. - * @param definingClassAndLoader the class in whose ClassValue we'll store the generated adapter, and its class loader. - * @throws AdaptationException if the adapter can not be generated for some reason. - */ - private JavaAdapterFactory(final Class<?> superType, final List<Class<?>> interfaces, final ClassAndLoader definingClassAndLoader) throws AdaptationException { - assert superType != null && !superType.isInterface(); - assert interfaces != null; - assert definingClassAndLoader != null; - - this.superClass = superType; - this.commonLoader = findCommonLoader(definingClassAndLoader); - cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) { - @Override - protected String getCommonSuperClass(final String type1, final String type2) { - // We need to override ClassWriter.getCommonSuperClass to use this factory's commonLoader as a class - // loader to find the common superclass of two types when needed. - return JavaAdapterFactory.this.getCommonSuperClass(type1, type2); - } - }; - superClassName = Type.getInternalName(superType); - generatedClassName = getGeneratedClassName(superType, interfaces); - - // Randomize the name of the privileged global setter, to make it non-feasible to find. - final long l; - synchronized(random) { - l = random.nextLong(); - } - // NOTE: they way this class name is calculated affects the value of MAX_GENERATED_TYPE_NAME_LENGTH constant. If - // you change the calculation of globalSetterClassName, adjust the constant too. - globalSetterClassName = generatedClassName.concat("$" + Long.toHexString(l & Long.MAX_VALUE)); - cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, generatedClassName, null, superClassName, getInternalTypeNames(interfaces)); - cw.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); - usedFieldNames.add(GLOBAL_FIELD_NAME); - - gatherMethods(superType); - gatherMethods(interfaces); - samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null; - generateFields(); - generateConstructors(); - generateMethods(); - // } - cw.visitEnd(); - } - - private static String getGeneratedClassName(final Class<?> superType, final List<Class<?>> interfaces) { - // The class we use to primarily name our adapter is either the superclass, or if it is Object (meaning we're - // just implementing interfaces or extending Object), then the first implemented interface or Object. - final Class<?> namingType = superType == Object.class ? (interfaces.isEmpty()? Object.class : interfaces.get(0)) : superType; - final Package pkg = namingType.getPackage(); - final String namingTypeName = Type.getInternalName(namingType); - final StringBuilder buf = new StringBuilder(); - if (namingTypeName.startsWith(JAVA_PACKAGE_PREFIX) || pkg == null || pkg.isSealed()) { - // Can't define new classes in java.* packages - buf.append(ADAPTER_PACKAGE_PREFIX).append(namingTypeName); - } else { - buf.append(namingTypeName).append(ADAPTER_CLASS_NAME_SUFFIX); - } - final Iterator<Class<?>> it = interfaces.iterator(); - if(superType == Object.class && it.hasNext()) { - it.next(); // Skip first interface, it was used to primarily name the adapter - } - // Append interface names to the adapter name - while(it.hasNext()) { - buf.append("$$").append(it.next().getSimpleName()); - } - return buf.toString().substring(0, Math.min(MAX_GENERATED_TYPE_NAME_LENGTH, buf.length())); - } - - /** - * Given a list of class objects, return an array with their binary names. Used to generate the array of interface - * names to implement. - * @param classes the classes - * @return an array of names - */ - private static String[] getInternalTypeNames(final List<Class<?>> classes) { - final int interfaceCount = classes.size(); - final String[] interfaceNames = new String[interfaceCount]; - for(int i = 0; i < interfaceCount; ++i) { - interfaceNames[i] = Type.getInternalName(classes.get(i)); - } - return interfaceNames; - } - - /** - * Utility method used by few other places in the code. Tests if the class has the abstract modifier and is not an - * array class. For some reason, array classes have the abstract modifier set in HotSpot JVM, and we don't want to - * treat array classes as abstract. - * @param clazz the inspected class - * @return true if the class is abstract and is not an array type. - */ - static boolean isAbstractClass(final Class<?> clazz) { - return Modifier.isAbstract(clazz.getModifiers()) && !clazz.isArray(); - } - /** * Returns an adapter class for the specified original types. The adapter class extends/implements the original * class/interfaces. @@ -346,55 +88,16 @@ public final class JavaAdapterFactory { * in the list already implement/extend, or {@code java.lang.Object} in a list of types consisting purely of * interfaces) will result in a different adapter class, even though those adapter classes are functionally * identical; we deliberately don't want to incur the additional processing cost of canonicalizing type lists. + * @param classOverrides a JavaScript object with functions serving as the class-level overrides and + * implementations. These overrides are defined for all instances of the class, and can be further overridden on a + * per-instance basis by passing additional objects in the constructor. * @return an adapter class. See this class' documentation for details on the generated adapter class. * @throws ECMAException with a TypeError if the adapter class can not be generated because the original class is * final, non-public, or has no public or protected constructors. */ - public static StaticClass getAdapterClassFor(final Class<?>[] types) { + public static StaticClass getAdapterClassFor(final Class<?>[] types, ScriptObject classOverrides) { assert types != null && types.length > 0; - final AdapterInfo adapterInfo = getAdapterInfo(types); - - final StaticClass clazz = adapterInfo.adapterClass; - if (clazz != null) { - return clazz; - } - adapterInfo.adaptationOutcome.typeError(); - - throw new AssertionError(); - } - - private static AdapterInfo getAdapterInfo(final Class<?>[] types) { - final ClassAndLoader definingClassAndLoader = getDefiningClassAndLoader(types); - - final Map<List<Class<?>>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.clazz); - final List<Class<?>> typeList = types.length == 1 ? getSingletonClassList(types[0]) : Arrays.asList(types.clone()); - AdapterInfo adapterInfo; - synchronized(adapterInfoMap) { - adapterInfo = adapterInfoMap.get(typeList); - if(adapterInfo == null) { - adapterInfo = createAdapterInfo(types, definingClassAndLoader); - adapterInfoMap.put(typeList, adapterInfo); - } - } - return adapterInfo; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static List<Class<?>> getSingletonClassList(final Class<?> clazz) { - return (List)Collections.singletonList(clazz); - } - - /** - * Returns whether an instance of the specified class/interface can be generated from a ScriptFunction. Returns true - * iff: the adapter for the class/interface can be created, it is abstract (this includes interfaces), it has at - * least one abstract method, all the abstract methods share the same name, and it has a public or protected default - * constructor. Note that invoking this class will most likely result in the adapter class being defined in the JVM - * if it hasn't been already. - * @param clazz the inspected class - * @return true iff an instance of the specified class/interface can be generated from a ScriptFunction. - */ - static boolean isAutoConvertibleFromFunction(final Class<?> clazz) { - return getAdapterInfo(new Class<?>[] { clazz }).autoConvertibleFromFunction; + return getAdapterInfo(types).getAdapterClassFor(classOverrides); } /** @@ -410,7 +113,7 @@ public final class JavaAdapterFactory { * @throws Exception if anything goes wrong */ public static MethodHandle getConstructor(final Class<?> sourceType, final Class<?> targetType) throws Exception { - final StaticClass adapterClass = getAdapterClassFor(new Class<?>[] { targetType }); + final StaticClass adapterClass = getAdapterClassFor(new Class<?>[] { targetType }, null); return AccessController.doPrivileged(new PrivilegedExceptionAction<MethodHandle>() { @Override public MethodHandle run() throws Exception { @@ -422,700 +125,46 @@ public final class JavaAdapterFactory { } /** - * Finishes the bytecode generation for the adapter class that was started in the constructor, and loads the - * bytecode as a new class into the JVM. - * @return the generated adapter class - */ - private Class<?> generateClass() { - final String binaryName = generatedClassName.replace('/', '.'); - try { - return Class.forName(binaryName, true, createClassLoader(commonLoader, binaryName, cw.toByteArray(), - globalSetterClassName.replace('/', '.'))); - } catch (final ClassNotFoundException e) { - throw new AssertionError(e); // cannot happen - } - } - - /** * Tells if the given Class is an adapter or support class * @param clazz Class object * @return true if the Class given is adapter or support class */ public static boolean isAdapterClass(Class<?> clazz) { - return clazz.getClassLoader() instanceof AdapterLoader; - } - - private static class AdapterLoader extends SecureClassLoader { - AdapterLoader(ClassLoader parent) { - super(parent); - } - } - - // Creation of class loader is in a separate static method so that it doesn't retain a reference to the factory - // instance. Note that the adapter class is created in the protection domain of the class/interface being - // extended/implemented, and only the privileged global setter action class is generated in the protection domain - // of Nashorn itself. Also note that the creation and loading of the global setter is deferred until it is - // required by JVM linker, which will only happen on first invocation of any of the adapted method. We could defer - // it even more by separating its invocation into a separate static method on the adapter class, but then someone - // with ability to introspect on the class and use setAccessible(true) on it could invoke the method. It's a - // security tradeoff... - private static ClassLoader createClassLoader(final ClassLoader parentLoader, final String className, - final byte[] classBytes, final String privilegedActionClassName) { - return new AdapterLoader(parentLoader) { - private final ClassLoader myLoader = getClass().getClassLoader(); - private final ProtectionDomain myProtectionDomain = getClass().getProtectionDomain(); - - @Override - public Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException { - try { - return super.loadClass(name, resolve); - } catch (final SecurityException se) { - // we may be implementing an interface or extending a class that was - // loaded by a loader that prevents package.access. If so, it'd throw - // SecurityException for nashorn's classes!. For adapter's to work, we - // should be able to refer to nashorn classes. - if (name.startsWith("jdk.nashorn.internal.")) { - return myLoader.loadClass(name); - } - throw se; - } - } - - @Override - protected Class<?> findClass(final String name) throws ClassNotFoundException { - if(name.equals(className)) { - final byte[] bytes = classBytes; - return defineClass(name, bytes, 0, bytes.length, GENERATED_PROTECTION_DOMAIN); - } else if(name.equals(privilegedActionClassName)) { - final byte[] bytes = generatePrivilegedActionClassBytes(privilegedActionClassName.replace('.', '/')); - return defineClass(name, bytes, 0, bytes.length, myProtectionDomain); - } else { - throw new ClassNotFoundException(name); - } - } - }; - } - - /** - * Generates a PrivilegedAction implementation class for invoking {@link Context#setGlobal(ScriptObject)} from the - * adapter class. - */ - private static byte[] generatePrivilegedActionClassBytes(final String className) { - final ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - // class GlobalSetter implements PrivilegedAction { - w.visit(Opcodes.V1_7, ACC_SUPER | ACC_FINAL, className, null, OBJECT_TYPE_NAME, new String[] { - PRIVILEGED_ACTION_TYPE_NAME - }); - - // private final ScriptObject global; - w.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); - - // private GlobalSetter(ScriptObject global) { - InstructionAdapter mv = new InstructionAdapter(w.visitMethod(ACC_PRIVATE, INIT, - SET_GLOBAL_METHOD_DESCRIPTOR, null, new String[0])); - mv.visitCode(); - // super(); - mv.visitVarInsn(ALOAD, 0); - mv.invokespecial(OBJECT_TYPE_NAME, INIT, VOID_NOARG); - // this.global = global; - mv.visitVarInsn(ALOAD, 0); - mv.visitVarInsn(ALOAD, 1); - mv.putfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); - - mv.visitInsn(RETURN); - mv.visitEnd(); - mv.visitMaxs(0, 0); - - // public Object run() { - mv = new InstructionAdapter(w.visitMethod(ACC_PUBLIC, "run", PRIVILEGED_RUN_METHOD_DESCRIPTOR, null, - new String[0])); - mv.visitCode(); - // Context.setGlobal(this.global); - mv.visitVarInsn(ALOAD, 0); - mv.getfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); - mv.invokestatic(CONTEXT_TYPE_NAME, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR); - // return null; - mv.visitInsn(ACONST_NULL); - mv.visitInsn(ARETURN); - - mv.visitEnd(); - mv.visitMaxs(0, 0); - - // static void setGlobal(ScriptObject global) { - mv = new InstructionAdapter(w.visitMethod(ACC_STATIC, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR, null, - new String[0])); - mv.visitCode(); - // new GlobalSetter(ScriptObject global) - mv.anew(Type.getType("L" + className + ";")); - mv.dup(); - mv.visitVarInsn(ALOAD, 0); - mv.invokespecial(className, INIT, SET_GLOBAL_METHOD_DESCRIPTOR); - // AccessController.doPrivileged(...) - mv.invokestatic(Type.getInternalName(AccessController.class), "doPrivileged", Type.getMethodDescriptor( - OBJECT_TYPE, PRIVILEGED_ACTION_TYPE)); - mv.pop(); - mv.visitInsn(RETURN); - - mv.visitEnd(); - mv.visitMaxs(0, 0); - - return w.toByteArray(); - } - - private void generateFields() { - for (final MethodInfo mi: methodInfos) { - cw.visitField(ACC_PRIVATE | ACC_FINAL, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd(); - } - } - - private void generateConstructors() throws AdaptationException { - boolean gotCtor = false; - for (final Constructor<?> ctor: superClass.getDeclaredConstructors()) { - final int modifier = ctor.getModifiers(); - if((modifier & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) { - generateConstructor(ctor); - gotCtor = true; - } - } - if(!gotCtor) { - throw new AdaptationException(AdaptationOutcome.ERROR_NO_ACCESSIBLE_CONSTRUCTOR, superClass.getCanonicalName()); - } - } - - boolean isAutoConvertibleFromFunction() { - return autoConvertibleFromFunction; - } - - private void generateConstructor(final Constructor<?> ctor) { - // Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the - // beginning of its parameter list. - generateConstructor(ctor, false); - - if (samName != null) { - if (!autoConvertibleFromFunction && ctor.getParameterTypes().length == 0) { - // If the original type only has a single abstract method name, as well as a default ctor, then it can - // be automatically converted from JS function. - autoConvertibleFromFunction = true; - } - // If all our abstract methods have a single name, generate an additional constructor, one that takes a - // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods. - generateConstructor(ctor, true); - } - } - - /** - * Generates a constructor for the adapter class. This constructor will take the same arguments as the supertype - * constructor passed as the argument here, and delegate to it. However, it will take an additional argument of - * either ScriptObject or ScriptFunction type (based on the value of the "fromFunction" parameter), and initialize - * all the method handle fields of the adapter instance with functions from the script object (or the script - * function itself, if that's what's passed). There is one method handle field in the adapter class for every method - * that can be implemented or overridden; the name of every field is same as the name of the method, with a number - * suffix that makes it unique in case of overloaded methods. The generated constructor will invoke - * {@link #getHandle(ScriptFunction, MethodType, boolean)} or {@link #getHandle(Object, String, MethodType, - * boolean)} to obtain the method handles; these methods make sure to add the necessary conversions and arity - * adjustments so that the resulting method handles can be invoked from generated methods using {@code invokeExact}. - * The constructor that takes a script function will only initialize the methods with the same name as the single - * abstract method. The constructor will also store the Nashorn global that was current at the constructor - * invocation time in a field named "global". The generated constructor will be public, regardless of whether the - * supertype constructor was public or protected. The generated constructor will not be variable arity, even if the - * supertype constructor was. - * @param ctor the supertype constructor that is serving as the base for the generated constructor. - * @param fromFunction true if we're generating a constructor that initializes SAM types from a single - * ScriptFunction passed to it, false if we're generating a constructor that initializes an arbitrary type from a - * ScriptObject passed to it. - */ - private void generateConstructor(final Constructor<?> ctor, final boolean fromFunction) { - final Type originalCtorType = Type.getType(ctor); - final Type[] originalArgTypes = originalCtorType.getArgumentTypes(); - final int argLen = originalArgTypes.length; - final Type[] newArgTypes = new Type[argLen + 1]; - - // Insert ScriptFunction|Object as the last argument to the constructor - final Type extraArgumentType = fromFunction ? SCRIPT_FUNCTION_TYPE : OBJECT_TYPE; - newArgTypes[argLen] = extraArgumentType; - System.arraycopy(originalArgTypes, 0, newArgTypes, 0, argLen); - - // All constructors must be public, even if in the superclass they were protected. - // Existing super constructor <init>(this, args...) triggers generating <init>(this, scriptObj, args...). - final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, INIT, - Type.getMethodDescriptor(originalCtorType.getReturnType(), newArgTypes), null, null)); - - mv.visitCode(); - // First, invoke super constructor with original arguments. If the form of the constructor we're generating is - // <init>(this, args..., scriptFn), then we're invoking super.<init>(this, args...). - mv.visitVarInsn(ALOAD, 0); - final Class<?>[] argTypes = ctor.getParameterTypes(); - int offset = 1; // First arg is at position 1, after this. - for (int i = 0; i < argLen; ++i) { - final Type argType = Type.getType(argTypes[i]); - mv.load(offset, argType); - offset += argType.getSize(); - } - mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor()); - - // Get a descriptor to the appropriate "JavaAdapterFactory.getHandle" method. - final String getHandleDescriptor = fromFunction ? GET_HANDLE_FUNCTION_DESCRIPTOR : GET_HANDLE_OBJECT_DESCRIPTOR; - - // Assign MethodHandle fields through invoking getHandle() - for (final MethodInfo mi : methodInfos) { - mv.visitVarInsn(ALOAD, 0); - if (fromFunction && !mi.getName().equals(samName)) { - // Constructors initializing from a ScriptFunction only initialize methods with the SAM name. - // NOTE: if there's a concrete overloaded method sharing the SAM name, it'll be overriden too. This - // is a deliberate design choice. All other method handles are initialized to null. - mv.visitInsn(ACONST_NULL); - } else { - mv.visitVarInsn(ALOAD, offset); - if(!fromFunction) { - mv.aconst(mi.getName()); - } - mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString())); - mv.iconst(mi.method.isVarArgs() ? 1 : 0); - mv.invokestatic(THIS_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor); - } - mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); - } - - // Assign "this.global = Context.getGlobal()" - mv.visitVarInsn(ALOAD, 0); - invokeGetGlobal(mv); - mv.dup(); - mv.invokevirtual(OBJECT_TYPE_NAME, "getClass", GET_CLASS_METHOD_DESCRIPTOR); // check against null Context - mv.pop(); - mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); - - // Wrap up - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - private static void invokeGetGlobal(final InstructionAdapter mv) { - mv.invokestatic(CONTEXT_TYPE_NAME, "getGlobal", GET_GLOBAL_METHOD_DESCRIPTOR); - } - - private void invokeSetGlobal(final InstructionAdapter mv) { - mv.invokestatic(globalSetterClassName, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR); - } - - /** - * Given a JS script function, binds it to null JS "this", and adapts its parameter types, return types, and arity - * to the specified type and arity. This method is public mainly for implementation reasons, so the adapter classes - * can invoke it from their constructors that take a ScriptFunction in its first argument to obtain the method - * handles for their abstract method implementations. - * @param fn the script function - * @param type the method type it has to conform to - * @param varArg if the Java method for which the function is being adapted is a variable arity method - * @return the appropriately adapted method handle for invoking the script function. - */ - public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type, final boolean varArg) { - // JS "this" will be null for SAMs - return adaptHandle(fn.getBoundInvokeHandle(null), type, varArg); - } - - /** - * Given a JS script object, retrieves a function from it by name, binds it to the script object as its "this", and - * adapts its parameter types, return types, and arity to the specified type and arity. This method is public mainly - * for implementation reasons, so the adapter classes can invoke it from their constructors that take a Object - * in its first argument to obtain the method handles for their method implementations. - * @param obj the script obj - * @param name the name of the property that contains the function - * @param type the method type it has to conform to - * @param varArg if the Java method for which the function is being adapted is a variable arity method - * @return the appropriately adapted method handle for invoking the script function, or null if the value of the - * property is either null or undefined, or "toString" was requested as the name, but the object doesn't directly - * define it but just inherits it through prototype. - */ - public static MethodHandle getHandle(final Object obj, final String name, final MethodType type, final boolean varArg) { - if (! (obj instanceof ScriptObject)) { - throw typeError("not.an.object", ScriptRuntime.safeToString(obj)); - } - - final ScriptObject sobj = (ScriptObject)obj; - // Since every JS Object has a toString, we only override "String toString()" it if it's explicitly specified - if ("toString".equals(name) && !sobj.hasOwnProperty("toString")) { - return null; - } - - final Object fnObj = sobj.get(name); - if (fnObj instanceof ScriptFunction) { - return adaptHandle(((ScriptFunction)fnObj).getBoundInvokeHandle(sobj), type, varArg); - } else if(fnObj == null || fnObj instanceof Undefined) { - return null; - } else { - throw typeError("not.a.function", name); - } - } - - private static MethodHandle adaptHandle(final MethodHandle handle, final MethodType type, final boolean varArg) { - return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(handle, type, varArg), type); - } - - /** - * Encapsulation of the information used to generate methods in the adapter classes. Basically, a wrapper around the - * reflective Method object, a cached MethodType, and the name of the field in the adapter class that will hold the - * method handle serving as the implementation of this method in adapter instances. - * - */ - private static class MethodInfo { - private final Method method; - private final MethodType type; - private String methodHandleFieldName; - - private MethodInfo(final Class<?> clazz, final String name, final Class<?>... argTypes) throws NoSuchMethodException { - this(clazz.getDeclaredMethod(name, argTypes)); - } - - private MethodInfo(final Method method) { - this.method = method; - this.type = MH.type(method.getReturnType(), method.getParameterTypes()); - } - - @Override - public boolean equals(final Object obj) { - return obj instanceof MethodInfo && equals((MethodInfo)obj); - } - - private boolean equals(final MethodInfo other) { - // Only method name and type are used for comparison; method handle field name is not. - return getName().equals(other.getName()) && type.equals(other.type); - } - - String getName() { - return method.getName(); - } - - @Override - public int hashCode() { - return getName().hashCode() ^ type.hashCode(); - } - - void setIsCanonical(final Set<String> usedFieldNames) { - int i = 0; - String fieldName = getName(); - while(!usedFieldNames.add(fieldName)) { - fieldName = getName() + (i++); - } - methodHandleFieldName = fieldName; - } - } - - private void generateMethods() { - for(final MethodInfo mi: methodInfos) { - generateMethod(mi); - } + return JavaAdapterClassLoader.isAdapterClass(clazz); } /** - * Generates a method in the adapter class that adapts a method from the original class. The generated methods will - * inspect the method handle field assigned to them. If it is null (the JS object doesn't provide an implementation - * for the method) then it will either invoke its version in the supertype, or if it is abstract, throw an - * {@link UnsupportedOperationException}. Otherwise, if the method handle field's value is not null, the handle is - * invoked using invokeExact (signature polymorphic invocation as per JLS 15.12.3). Before the invocation, the - * current Nashorn {@link Context} is checked, and if it is different than the global used to create the adapter - * instance, the creating global is set to be the current global. In this case, the previously current global is - * restored after the invocation. If invokeExact results in a Throwable that is not one of the method's declared - * exceptions, and is not an unchecked throwable, then it is wrapped into a {@link RuntimeException} and the runtime - * exception is thrown. The method handle retrieved from the field is guaranteed to exactly match the signature of - * the method; this is guaranteed by the way constructors of the adapter class obtain them using - * {@link #getHandle(Object, String, MethodType, boolean)}. - * @param mi the method info describing the method to be generated. - */ - private void generateMethod(final MethodInfo mi) { - final Method method = mi.method; - final int mod = method.getModifiers(); - final int access = ACC_PUBLIC | (method.isVarArgs() ? ACC_VARARGS : 0); - final Class<?>[] exceptions = method.getExceptionTypes(); - final String[] exceptionNames = new String[exceptions.length]; - for (int i = 0; i < exceptions.length; ++i) { - exceptionNames[i] = Type.getInternalName(exceptions[i]); - } - final MethodType type = mi.type; - final String methodDesc = type.toMethodDescriptorString(); - final String name = mi.getName(); - - final Type asmType = Type.getMethodType(methodDesc); - final Type[] asmArgTypes = asmType.getArgumentTypes(); - - // Determine the first index for a local variable - int nextLocalVar = 1; // this - for(final Type t: asmArgTypes) { - nextLocalVar += t.getSize(); - } - - final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(access, name, methodDesc, null, - exceptionNames)); - mv.visitCode(); - - final Label methodHandleNotNull = new Label(); - final Label methodEnd = new Label(); - - final Type returnType = Type.getType(type.returnType()); - - // Get the method handle - mv.visitVarInsn(ALOAD, 0); - mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); - mv.visitInsn(DUP); // It'll remain on the stack all the way until the invocation - // Check if the method handle is null - mv.visitJumpInsn(IFNONNULL, methodHandleNotNull); - if(Modifier.isAbstract(mod)) { - // If it's null, and the method is abstract, throw an exception - mv.anew(UNSUPPORTED_OPERATION_TYPE); - mv.dup(); - mv.invokespecial(UNSUPPORTED_OPERATION_TYPE_NAME, INIT, VOID_NOARG); - mv.athrow(); - } else { - // If it's null, and the method is not abstract, delegate to super method. - mv.visitVarInsn(ALOAD, 0); - int nextParam = 1; - for(final Type t: asmArgTypes) { - mv.load(nextParam, t); - nextParam += t.getSize(); - } - mv.invokespecial(superClassName, name, methodDesc); - mv.areturn(returnType); - } - - mv.visitLabel(methodHandleNotNull); - final int currentGlobalVar = nextLocalVar++; - final int globalsDifferVar = nextLocalVar++; - - // Emit code for switching to the creating global - // ScriptObject currentGlobal = Context.getGlobal(); - invokeGetGlobal(mv); - mv.dup(); - mv.visitVarInsn(ASTORE, currentGlobalVar); - // if(this.global == currentGlobal) { - loadGlobalOnStack(mv); - final Label globalsDiffer = new Label(); - mv.ifacmpne(globalsDiffer); - // globalsDiffer = false - mv.iconst(0); // false - final Label proceed = new Label(); - mv.goTo(proceed); - mv.visitLabel(globalsDiffer); - // } else { - // Context.setGlobal(this.global); - loadGlobalOnStack(mv); - invokeSetGlobal(mv); - // globalsDiffer = true - mv.iconst(1); - - mv.visitLabel(proceed); - mv.visitVarInsn(ISTORE, globalsDifferVar); - - // Load all parameters back on stack for dynamic invocation. - int varOffset = 1; - for (final Type t : asmArgTypes) { - mv.load(varOffset, t); - varOffset += t.getSize(); - } - - // Invoke the target method handle - final Label tryBlockStart = new Label(); - mv.visitLabel(tryBlockStart); - mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString()); - final Label tryBlockEnd = new Label(); - mv.visitLabel(tryBlockEnd); - emitFinally(mv, currentGlobalVar, globalsDifferVar); - mv.areturn(returnType); - - // If Throwable is not declared, we need an adapter from Throwable to RuntimeException - final boolean throwableDeclared = isThrowableDeclared(exceptions); - final Label throwableHandler; - if (!throwableDeclared) { - // Add "throw new RuntimeException(Throwable)" handler for Throwable - throwableHandler = new Label(); - mv.visitLabel(throwableHandler); - mv.anew(RUNTIME_EXCEPTION_TYPE); - mv.dupX1(); - mv.swap(); - mv.invokespecial(RUNTIME_EXCEPTION_TYPE_NAME, INIT, Type.getMethodDescriptor(Type.VOID_TYPE, THROWABLE_TYPE)); - // Fall through to rethrow handler - } else { - throwableHandler = null; - } - final Label rethrowHandler = new Label(); - mv.visitLabel(rethrowHandler); - // Rethrow handler for RuntimeException, Error, and all declared exception types - emitFinally(mv, currentGlobalVar, globalsDifferVar); - mv.athrow(); - mv.visitLabel(methodEnd); - - mv.visitLocalVariable("currentGlobal", SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, methodHandleNotNull, methodEnd, currentGlobalVar); - mv.visitLocalVariable("globalsDiffer", Type.INT_TYPE.getDescriptor(), null, methodHandleNotNull, methodEnd, globalsDifferVar); - - if(throwableDeclared) { - mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, THROWABLE_TYPE_NAME); - assert throwableHandler == null; - } else { - mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, RUNTIME_EXCEPTION_TYPE_NAME); - mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, ERROR_TYPE_NAME); - for(final String excName: exceptionNames) { - mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, excName); - } - mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, throwableHandler, THROWABLE_TYPE_NAME); - } - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - /** - * Emit code to restore the previous Nashorn Context when needed. - * @param mv the instruction adapter - * @param currentGlobalVar index of the local variable holding the reference to the current global at method - * entry. - * @param globalsDifferVar index of the boolean local variable that is true if the global needs to be restored. - */ - private void emitFinally(final InstructionAdapter mv, final int currentGlobalVar, final int globalsDifferVar) { - // Emit code to restore the previous Nashorn global if needed - mv.visitVarInsn(ILOAD, globalsDifferVar); - final Label skip = new Label(); - mv.ifeq(skip); - mv.visitVarInsn(ALOAD, currentGlobalVar); - invokeSetGlobal(mv); - mv.visitLabel(skip); - } - - private void loadGlobalOnStack(final InstructionAdapter mv) { - mv.visitVarInsn(ALOAD, 0); - mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); - } - - private static boolean isThrowableDeclared(final Class<?>[] exceptions) { - for (final Class<?> exception : exceptions) { - if (exception == Throwable.class) { - return true; - } - } - return false; - } - - /** - * Gathers methods that can be implemented or overridden from the specified type into this factory's - * {@link #methodInfos} set. It will add all non-final, non-static methods that are either public or protected from - * the type if the type itself is public. If the type is a class, the method will recursively invoke itself for its - * superclass and the interfaces it implements, and add further methods that were not directly declared on the - * class. - * @param type the type defining the methods. + * Returns whether an instance of the specified class/interface can be generated from a ScriptFunction. Returns true + * iff: the adapter for the class/interface can be created, it is abstract (this includes interfaces), it has at + * least one abstract method, all the abstract methods share the same name, and it has a public or protected default + * constructor. Note that invoking this class will most likely result in the adapter class being defined in the JVM + * if it hasn't been already. + * @param clazz the inspected class + * @return true iff an instance of the specified class/interface can be generated from a ScriptFunction. */ - private void gatherMethods(final Class<?> type) { - if (Modifier.isPublic(type.getModifiers())) { - final Method[] typeMethods = type.isInterface() ? type.getMethods() : type.getDeclaredMethods(); - - for (final Method typeMethod: typeMethods) { - final int m = typeMethod.getModifiers(); - if (Modifier.isStatic(m)) { - continue; - } - if (Modifier.isPublic(m) || Modifier.isProtected(m)) { - final MethodInfo mi = new MethodInfo(typeMethod); - if (Modifier.isFinal(m)) { - finalMethods.add(mi); - } else if (!finalMethods.contains(mi) && methodInfos.add(mi)) { - if (Modifier.isAbstract(m)) { - abstractMethodNames.add(mi.getName()); - } - mi.setIsCanonical(usedFieldNames); - } - } - } - } - // If the type is a class, visit its superclasses and declared interfaces. If it's an interface, we're done. - // Needing to invoke the method recursively for a non-interface Class object is the consequence of needing to - // see all declared protected methods, and Class.getDeclaredMethods() doesn't provide those declared in a - // superclass. For interfaces, we used Class.getMethods(), as we're only interested in public ones there, and - // getMethods() does provide those declared in a superinterface. - if (!type.isInterface()) { - final Class<?> superType = type.getSuperclass(); - if (superType != null) { - gatherMethods(superType); - } - for (final Class<?> itf: type.getInterfaces()) { - gatherMethods(itf); - } - } + static boolean isAutoConvertibleFromFunction(final Class<?> clazz) { + return getAdapterInfo(new Class<?>[] { clazz }).autoConvertibleFromFunction; } - private void gatherMethods(final List<Class<?>> classes) { - for(final Class<?> c: classes) { - gatherMethods(c); - } - } + private static AdapterInfo getAdapterInfo(final Class<?>[] types) { + final ClassAndLoader definingClassAndLoader = ClassAndLoader.getDefiningClassAndLoader(types); - /** - * Creates a collection of methods that are not final, but we still never allow them to be overridden in adapters, - * as explicitly declaring them automatically is a bad idea. Currently, this means {@code Object.finalize()} and - * {@code Object.clone()}. - * @return a collection of method infos representing those methods that we never override in adapter classes. - */ - private static Collection<MethodInfo> getExcludedMethods() { - return AccessController.doPrivileged(new PrivilegedAction<Collection<MethodInfo>>() { - @Override - public Collection<MethodInfo> run() { - try { - return Arrays.asList( - new MethodInfo(Object.class, "finalize"), - new MethodInfo(Object.class, "clone")); - } catch (final NoSuchMethodException e) { - throw new AssertionError(e); - } + final Map<List<Class<?>>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.getRepresentativeClass()); + final List<Class<?>> typeList = types.length == 1 ? getSingletonClassList(types[0]) : Arrays.asList(types.clone()); + AdapterInfo adapterInfo; + synchronized(adapterInfoMap) { + adapterInfo = adapterInfoMap.get(typeList); + if(adapterInfo == null) { + adapterInfo = createAdapterInfo(types, definingClassAndLoader); + adapterInfoMap.put(typeList, adapterInfo); } - }); - } - - private static ProtectionDomain createGeneratedProtectionDomain() { - // Generated classes need to have AllPermission. Since we require the "createClassLoader" RuntimePermission, we - // can create a class loader that'll load new classes with any permissions. Our generated classes are just - // delegating adapters, so having AllPermission can't cause anything wrong; the effective set of permissions for - // the executing script functions will still be limited by the permissions of the caller and the permissions of - // the script. - final Permissions permissions = new Permissions(); - permissions.add(new AllPermission()); - return new ProtectionDomain(new CodeSource(null, (CodeSigner[])null), permissions); - } - - private static class AdapterInfo { - final StaticClass adapterClass; - final boolean autoConvertibleFromFunction; - final AnnotatedAdaptationOutcome adaptationOutcome; - - AdapterInfo(final StaticClass adapterClass, final boolean autoConvertibleFromFunction) { - this.adapterClass = adapterClass; - this.autoConvertibleFromFunction = autoConvertibleFromFunction; - this.adaptationOutcome = AnnotatedAdaptationOutcome.SUCCESS; - } - - AdapterInfo(final AdaptationOutcome outcome, final String classList) { - this(new AnnotatedAdaptationOutcome(outcome, classList)); - } - - AdapterInfo(final AnnotatedAdaptationOutcome adaptationOutcome) { - this.adapterClass = null; - this.autoConvertibleFromFunction = false; - this.adaptationOutcome = adaptationOutcome; } + return adapterInfo; } - /** - * An adaptation outcome accompanied with a name of a class (or a list of multiple class names) that are the reason - * an adapter could not be generated. - */ - private static class AnnotatedAdaptationOutcome { - static final AnnotatedAdaptationOutcome SUCCESS = new AnnotatedAdaptationOutcome(AdaptationOutcome.SUCCESS, ""); - - private final AdaptationOutcome adaptationOutcome; - private final String classList; - - AnnotatedAdaptationOutcome(final AdaptationOutcome adaptationOutcome, final String classList) { - this.adaptationOutcome = adaptationOutcome; - this.classList = classList; - } - - void typeError() { - assert adaptationOutcome != AdaptationOutcome.SUCCESS; - throw ECMAErrors.typeError("extend." + adaptationOutcome, classList); - } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static List<Class<?>> getSingletonClassList(final Class<?> clazz) { + return (List)Collections.singletonList(clazz); } /** @@ -1130,17 +179,17 @@ public final class JavaAdapterFactory { final int mod = t.getModifiers(); if(!t.isInterface()) { if(superClass != null) { - return new AdapterInfo(AdaptationOutcome.ERROR_MULTIPLE_SUPERCLASSES, t.getCanonicalName() + " and " + superClass.getCanonicalName()); + return new AdapterInfo(AdaptationResult.Outcome.ERROR_MULTIPLE_SUPERCLASSES, t.getCanonicalName() + " and " + superClass.getCanonicalName()); } if (Modifier.isFinal(mod)) { - return new AdapterInfo(AdaptationOutcome.ERROR_FINAL_CLASS, t.getCanonicalName()); + return new AdapterInfo(AdaptationResult.Outcome.ERROR_FINAL_CLASS, t.getCanonicalName()); } superClass = t; } else { interfaces.add(t); } if(!Modifier.isPublic(mod)) { - return new AdapterInfo(AdaptationOutcome.ERROR_NON_PUBLIC_CLASS, t.getCanonicalName()); + return new AdapterInfo(AdaptationResult.Outcome.ERROR_NON_PUBLIC_CLASS, t.getCanonicalName()); } } final Class<?> effectiveSuperClass = superClass == null ? Object.class : superClass; @@ -1148,211 +197,78 @@ public final class JavaAdapterFactory { @Override public AdapterInfo run() { try { - final JavaAdapterFactory factory = new JavaAdapterFactory(effectiveSuperClass, interfaces, definingClassAndLoader); - return new AdapterInfo(StaticClass.forClass(factory.generateClass()), - factory.isAutoConvertibleFromFunction()); + return new AdapterInfo(effectiveSuperClass, interfaces, definingClassAndLoader); } catch (final AdaptationException e) { - return new AdapterInfo(e.outcome); + return new AdapterInfo(e.getAdaptationResult()); } } }); } - @SuppressWarnings("serial") - private static class AdaptationException extends Exception { - private final AnnotatedAdaptationOutcome outcome; - AdaptationException(final AdaptationOutcome outcome, final String classList) { - this.outcome = new AnnotatedAdaptationOutcome(outcome, classList); - } - } - - private String getCommonSuperClass(final String type1, final String type2) { - try { - final Class<?> c1 = Class.forName(type1.replace('/', '.'), false, commonLoader); - final Class<?> c2 = Class.forName(type2.replace('/', '.'), false, commonLoader); - if (c1.isAssignableFrom(c2)) { - return type1; - } - if (c2.isAssignableFrom(c1)) { - return type2; - } - if (c1.isInterface() || c2.isInterface()) { - return "java/lang/Object"; - } - return assignableSuperClass(c1, c2).getName().replace('.', '/'); - } catch(final ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - private static Class<?> assignableSuperClass(final Class<?> c1, final Class<?> c2) { - final Class<?> superClass = c1.getSuperclass(); - return superClass.isAssignableFrom(c2) ? superClass : assignableSuperClass(superClass, c2); - } + private static class AdapterInfo { + private static final ClassAndLoader SCRIPT_OBJECT_LOADER = new ClassAndLoader(ScriptObject.class, true); - /** - * Choose between the passed class loader and the class loader that defines the ScriptObject class, based on which - * of the two can see the classes in both. - * @param classAndLoader the loader and a representative class from it that will be used to add the generated - * adapter to its ADAPTER_INFO_MAPS. - * @return the class loader that sees both the specified class and Nashorn classes. - * @throws IllegalStateException if no such class loader is found. - */ - private static ClassLoader findCommonLoader(final ClassAndLoader classAndLoader) throws AdaptationException { - final ClassLoader loader = classAndLoader.getLoader(); - if (canSeeClass(loader, ScriptObject.class)) { - return loader; - } + private final ClassLoader commonLoader; + private final JavaAdapterClassLoader adapterGenerator; + // Cacheable adapter class that is shared by all adapter instances that don't have class overrides, only + // instance overrides. + final StaticClass instanceAdapterClass; + final boolean autoConvertibleFromFunction; + final AdaptationResult adaptationResult; - final ClassLoader nashornLoader = ScriptObject.class.getClassLoader(); - if(canSeeClass(nashornLoader, classAndLoader.clazz)) { - return nashornLoader; + AdapterInfo(Class<?> superClass, List<Class<?>> interfaces, ClassAndLoader definingLoader) throws AdaptationException { + this.commonLoader = findCommonLoader(definingLoader); + final JavaAdapterBytecodeGenerator gen = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, false); + this.autoConvertibleFromFunction = gen.isAutoConvertibleFromFunction(); + this.instanceAdapterClass = gen.createAdapterClassLoader().generateClass(commonLoader); + this.adapterGenerator = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, true).createAdapterClassLoader(); + this.adaptationResult = AdaptationResult.SUCCESSFUL_RESULT; } - throw new AdaptationException(AdaptationOutcome.ERROR_NO_COMMON_LOADER, classAndLoader.clazz.getCanonicalName()); - } - - private static boolean canSeeClass(final ClassLoader cl, final Class<?> clazz) { - try { - return Class.forName(clazz.getName(), false, cl) == clazz; - } catch (final ClassNotFoundException e) { - return false; + AdapterInfo(final AdaptationResult.Outcome outcome, final String classList) { + this(new AdaptationResult(outcome, classList)); } - } - /** - * Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the - * list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a - * class loader that can also see all other types is returned. If there is no such loader, an exception is thrown. - * @param types the input types - * @return the first type from the array that is defined in a class loader that can also see all other types. - */ - private static ClassAndLoader getDefiningClassAndLoader(final Class<?>[] types) { - // Short circuit the cheap case - if(types.length == 1) { - return new ClassAndLoader(types[0], false); + AdapterInfo(final AdaptationResult adaptationResult) { + this.commonLoader = null; + this.adapterGenerator = null; + this.instanceAdapterClass = null; + this.autoConvertibleFromFunction = false; + this.adaptationResult = adaptationResult; } - return AccessController.doPrivileged(new PrivilegedAction<ClassAndLoader>() { - @Override - public ClassAndLoader run() { - return getDefiningClassAndLoaderPrivileged(types); + StaticClass getAdapterClassFor(ScriptObject classOverrides) { + if(adaptationResult.getOutcome() != AdaptationResult.Outcome.SUCCESS) { + throw adaptationResult.typeError(); } - }); - } - - private static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class<?>[] types) { - final Collection<ClassAndLoader> maximumVisibilityLoaders = getMaximumVisibilityLoaders(types); - - final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator(); - if(maximumVisibilityLoaders.size() == 1) { - // Fortunate case - single maximally specific class loader; return its representative class. - return it.next(); - } - - // Ambiguity; throw an error. - assert maximumVisibilityLoaders.size() > 1; // basically, can't be zero - final StringBuilder b = new StringBuilder(); - b.append(it.next().clazz.getCanonicalName()); - while(it.hasNext()) { - b.append(", ").append(it.next().clazz.getCanonicalName()); - } - throw typeError("extend.ambiguous.defining.class", b.toString()); - } - - /** - * Given an array of types, return a subset of their class loaders that are maximal according to the - * "can see other loaders' classes" relation, which is presumed to be a partial ordering. - * @param types types - * @return a collection of maximum visibility class loaders. It is guaranteed to have at least one element. - */ - private static Collection<ClassAndLoader> getMaximumVisibilityLoaders(final Class<?>[] types) { - final List<ClassAndLoader> maximumVisibilityLoaders = new LinkedList<>(); - outer: for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) { - final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator(); - while(it.hasNext()) { - final ClassAndLoader existingMax = it.next(); - final boolean candidateSeesExisting = canSeeClass(maxCandidate.getRetrievedLoader(), existingMax.clazz); - final boolean exitingSeesCandidate = canSeeClass(existingMax.getRetrievedLoader(), maxCandidate.clazz); - if(candidateSeesExisting) { - if(!exitingSeesCandidate) { - // The candidate sees the the existing maximum, so drop the existing one as it's no longer maximal. - it.remove(); - } - // NOTE: there's also the anomalous case where both loaders see each other. Not sure what to do - // about that one, as two distinct class loaders both seeing each other's classes is weird and - // violates the assumption that the relation "sees others' classes" is a partial ordering. We'll - // just not do anything, and treat them as incomparable; hopefully some later class loader that - // comes along can eliminate both of them, if it can not, we'll end up with ambiguity anyway and - // throw an error at the end. - } else if(exitingSeesCandidate) { - // Existing sees the candidate, so drop the candidate. - continue outer; - } + if(classOverrides == null) { + return instanceAdapterClass; } - // If we get here, no existing maximum visibility loader could see the candidate, so the candidate is a new - // maximum. - maximumVisibilityLoaders.add(maxCandidate); - } - return maximumVisibilityLoaders; - } - - private static Collection<ClassAndLoader> getClassLoadersForTypes(final Class<?>[] types) { - final Map<ClassAndLoader, ClassAndLoader> classesAndLoaders = new LinkedHashMap<>(); - for(final Class<?> c: types) { - final ClassAndLoader cl = new ClassAndLoader(c, true); - if(!classesAndLoaders.containsKey(cl)) { - classesAndLoaders.put(cl, cl); + JavaAdapterServices.setClassOverrides(classOverrides); + try { + return adapterGenerator.generateClass(commonLoader); + } finally { + JavaAdapterServices.setClassOverrides(null); } } - return classesAndLoaders.keySet(); - } - /** - * A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its - * equals/hashCode is defined in terms of the identity of the class loader. - */ - private static final class ClassAndLoader { - private final Class<?> clazz; - // Don't access this directly; most of the time, use getRetrievedLoader(), or if you know what you're doing, - // getLoader(). - private ClassLoader loader; - // We have mild affinity against eagerly retrieving the loader, as we need to do it in a privileged block. For - // the most basic case of looking up an already-generated adapter info for a single type, we avoid it. - private boolean loaderRetrieved; - - ClassAndLoader(final Class<?> clazz, final boolean retrieveLoader) { - this.clazz = clazz; - if(retrieveLoader) { - retrieveLoader(); + /** + * Choose between the passed class loader and the class loader that defines the ScriptObject class, based on which + * of the two can see the classes in both. + * @param classAndLoader the loader and a representative class from it that will be used to add the generated + * adapter to its ADAPTER_INFO_MAPS. + * @return the class loader that sees both the specified class and Nashorn classes. + * @throws IllegalStateException if no such class loader is found. + */ + private static ClassLoader findCommonLoader(final ClassAndLoader classAndLoader) throws AdaptationException { + if(classAndLoader.canSee(SCRIPT_OBJECT_LOADER)) { + return classAndLoader.getLoader(); } - } - - ClassLoader getLoader() { - if(!loaderRetrieved) { - retrieveLoader(); + if (SCRIPT_OBJECT_LOADER.canSee(classAndLoader)) { + return SCRIPT_OBJECT_LOADER.getLoader(); } - return getRetrievedLoader(); - } - - ClassLoader getRetrievedLoader() { - assert loaderRetrieved; - return loader; - } - - private void retrieveLoader() { - loader = clazz.getClassLoader(); - loaderRetrieved = true; - } - @Override - public boolean equals(final Object obj) { - return obj instanceof ClassAndLoader && ((ClassAndLoader)obj).getRetrievedLoader() == getRetrievedLoader(); - } - - @Override - public int hashCode() { - return System.identityHashCode(getRetrievedLoader()); + throw new AdaptationException(AdaptationResult.Outcome.ERROR_NO_COMMON_LOADER, classAndLoader.getRepresentativeClass().getCanonicalName()); } } } diff --git a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterGeneratorBase.java b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterGeneratorBase.java new file mode 100644 index 00000000..67499cbc --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterGeneratorBase.java @@ -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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.runtime.linker; + +import jdk.internal.org.objectweb.asm.Type; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptObject; + +/** + * Base class for both {@link JavaAdapterBytecodeGenerator} and {@link JavaAdapterClassLoader}, containing those + * bytecode types, type names and method descriptor that are used by both. + */ +abstract class JavaAdapterGeneratorBase { + static final Type CONTEXT_TYPE = Type.getType(Context.class); + static final Type OBJECT_TYPE = Type.getType(Object.class); + static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class); + + static final String CONTEXT_TYPE_NAME = CONTEXT_TYPE.getInternalName(); + static final String OBJECT_TYPE_NAME = OBJECT_TYPE.getInternalName(); + + static final String INIT = "<init>"; + + static final String GLOBAL_FIELD_NAME = "global"; + + static final String SCRIPT_OBJECT_TYPE_DESCRIPTOR = SCRIPT_OBJECT_TYPE.getDescriptor(); + + static final String SET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, SCRIPT_OBJECT_TYPE); + static final String VOID_NOARG_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE); + + protected JavaAdapterGeneratorBase() { + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java new file mode 100644 index 00000000..eb890b9b --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java @@ -0,0 +1,112 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.runtime.linker; + +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.ScriptRuntime; +import jdk.nashorn.internal.runtime.Undefined; + +/** + * Provides static utility services to generated Java adapter classes. + */ +public class JavaAdapterServices { + private static final ThreadLocal<ScriptObject> classOverrides = new ThreadLocal<>(); + + private JavaAdapterServices() { + } + + /** + * Given a JS script function, binds it to null JS "this", and adapts its parameter types, return types, and arity + * to the specified type and arity. This method is public mainly for implementation reasons, so the adapter classes + * can invoke it from their constructors that take a ScriptFunction in its first argument to obtain the method + * handles for their abstract method implementations. + * @param fn the script function + * @param type the method type it has to conform to + * @return the appropriately adapted method handle for invoking the script function. + */ + public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type) { + // JS "this" will be null for SAMs + return adaptHandle(fn.getBoundInvokeHandle(null), type); + } + + /** + * Given a JS script object, retrieves a function from it by name, binds it to the script object as its "this", and + * adapts its parameter types, return types, and arity to the specified type and arity. This method is public mainly + * for implementation reasons, so the adapter classes can invoke it from their constructors that take a Object + * in its first argument to obtain the method handles for their method implementations. + * @param obj the script obj + * @param name the name of the property that contains the function + * @param type the method type it has to conform to + * @return the appropriately adapted method handle for invoking the script function, or null if the value of the + * property is either null or undefined, or "toString" was requested as the name, but the object doesn't directly + * define it but just inherits it through prototype. + */ + public static MethodHandle getHandle(final Object obj, final String name, final MethodType type) { + if (! (obj instanceof ScriptObject)) { + throw typeError("not.an.object", ScriptRuntime.safeToString(obj)); + } + + final ScriptObject sobj = (ScriptObject)obj; + // Since every JS Object has a toString, we only override "String toString()" it if it's explicitly specified + if ("toString".equals(name) && !sobj.hasOwnProperty("toString")) { + return null; + } + + final Object fnObj = sobj.get(name); + if (fnObj instanceof ScriptFunction) { + return adaptHandle(((ScriptFunction)fnObj).getBoundInvokeHandle(sobj), type); + } else if(fnObj == null || fnObj instanceof Undefined) { + return null; + } else { + throw typeError("not.a.function", name); + } + } + + /** + * Returns a thread-local JS object used to define methods for the adapter class being initialized on the current + * thread. This method is public solely for implementation reasons, so the adapter classes can invoke it from their + * static initializers. + * @return the thread-local JS object used to define methods for the class being initialized. + */ + public static ScriptObject getClassOverrides() { + final ScriptObject overrides = classOverrides.get(); + assert overrides != null; + return overrides; + } + + static void setClassOverrides(ScriptObject overrides) { + classOverrides.set(overrides); + } + + private static MethodHandle adaptHandle(final MethodHandle handle, final MethodType type) { + return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(handle, type, false), type); + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java b/src/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java index a2513912..8cb360c4 100644 --- a/src/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java +++ b/src/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java @@ -43,9 +43,8 @@ public class NashornCallSiteDescriptor extends AbstractCallSiteDescriptor { public static final int CALLSITE_SCOPE = 0x01; /** Flags that the call site is in code that uses ECMAScript strict mode. */ public static final int CALLSITE_STRICT = 0x02; - /** Flags that a property getter or setter call site references a scope variable that is not in the global scope - * (it is in a function lexical scope), and the function's scope object class is fixed and known in advance. Such - * getters and setters can often be linked more optimally using these assumptions. */ + /** Flags that a property getter or setter call site references a scope variable that is located at a known distance + * in the scope chain. Such getters and setters can often be linked more optimally using these assumptions. */ public static final int CALLSITE_FAST_SCOPE = 0x400; /** Flags that the call site is profiled; Contexts that have {@code "profile.callsites"} boolean property set emit diff --git a/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java b/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java index 3a4a5d77..e7cc78b0 100644 --- a/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java +++ b/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java @@ -29,6 +29,7 @@ import static jdk.nashorn.internal.lookup.Lookup.MH; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.reflect.Modifier; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.ConversionComparator; import jdk.internal.dynalink.linker.GuardedInvocation; @@ -131,10 +132,22 @@ final class NashornLinker implements TypeBasedGuardingDynamicLinker, GuardingTyp } private static boolean isAutoConvertibleFromFunction(final Class<?> clazz) { - return JavaAdapterFactory.isAbstractClass(clazz) && !ScriptObject.class.isAssignableFrom(clazz) && + return isAbstractClass(clazz) && !ScriptObject.class.isAssignableFrom(clazz) && JavaAdapterFactory.isAutoConvertibleFromFunction(clazz); } + /** + * Utility method used by few other places in the code. Tests if the class has the abstract modifier and is not an + * array class. For some reason, array classes have the abstract modifier set in HotSpot JVM, and we don't want to + * treat array classes as abstract. + * @param clazz the inspected class + * @return true if the class is abstract and is not an array type. + */ + static boolean isAbstractClass(final Class<?> clazz) { + return Modifier.isAbstract(clazz.getModifiers()) && !clazz.isArray(); + } + + @Override public Comparison compareConversion(final Class<?> sourceType, final Class<?> targetType1, final Class<?> targetType2) { if(ScriptObject.class.isAssignableFrom(sourceType)) { diff --git a/src/jdk/nashorn/internal/runtime/linker/NashornStaticClassLinker.java b/src/jdk/nashorn/internal/runtime/linker/NashornStaticClassLinker.java index 738162d5..67c1d8db 100644 --- a/src/jdk/nashorn/internal/runtime/linker/NashornStaticClassLinker.java +++ b/src/jdk/nashorn/internal/runtime/linker/NashornStaticClassLinker.java @@ -68,10 +68,10 @@ final class NashornStaticClassLinker implements TypeBasedGuardingDynamicLinker { if ("new".equals(desc.getNameToken(CallSiteDescriptor.OPERATOR))) { final Class<?> receiverClass = ((StaticClass) self).getRepresentedClass(); // Is the class abstract? (This includes interfaces.) - if (JavaAdapterFactory.isAbstractClass(receiverClass)) { + if (NashornLinker.isAbstractClass(receiverClass)) { // Change this link request into a link request on the adapter class. final Object[] args = request.getArguments(); - args[0] = JavaAdapterFactory.getAdapterClassFor(new Class<?>[] { receiverClass }); + args[0] = JavaAdapterFactory.getAdapterClassFor(new Class<?>[] { receiverClass }, null); final LinkRequest adapterRequest = request.replaceArguments(request.getCallSiteDescriptor(), args); final GuardedInvocation gi = checkNullConstructor(delegate(linkerServices, adapterRequest), receiverClass); // Finally, modify the guard to test for the original abstract class. diff --git a/src/jdk/nashorn/internal/runtime/options/Options.java b/src/jdk/nashorn/internal/runtime/options/Options.java index 3e09fa57..16f3bc0f 100644 --- a/src/jdk/nashorn/internal/runtime/options/Options.java +++ b/src/jdk/nashorn/internal/runtime/options/Options.java @@ -243,7 +243,13 @@ public final class Options { */ public String getString(final String key) { final Option<?> option = get(key); - return option != null ? (String)option.getValue() : null; + if(option != null) { + final String value = (String)option.getValue(); + if(value != null) { + return value.intern(); + } + } + return null; } /** diff --git a/src/jdk/nashorn/internal/runtime/regexp/RegExpFactory.java b/src/jdk/nashorn/internal/runtime/regexp/RegExpFactory.java index 6ff66f21..4a7ac11b 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/RegExpFactory.java +++ b/src/jdk/nashorn/internal/runtime/regexp/RegExpFactory.java @@ -40,7 +40,7 @@ public class RegExpFactory { private final static String JONI = "joni"; static { - final String impl = Options.getStringProperty("nashorn.regexp.impl", JDK); + final String impl = Options.getStringProperty("nashorn.regexp.impl", JONI); switch (impl) { case JONI: instance = new JoniRegExp.Factory(); diff --git a/src/jdk/nashorn/internal/runtime/regexp/RegExpScanner.java b/src/jdk/nashorn/internal/runtime/regexp/RegExpScanner.java index e8c60c4a..e651c44e 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/RegExpScanner.java +++ b/src/jdk/nashorn/internal/runtime/regexp/RegExpScanner.java @@ -26,11 +26,10 @@ package jdk.nashorn.internal.runtime.regexp; import java.util.HashMap; -import java.util.LinkedHashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.regex.PatternSyntaxException; import jdk.nashorn.internal.parser.Lexer; @@ -48,9 +47,6 @@ final class RegExpScanner extends Scanner { */ private final StringBuilder sb; - /** Is this the special case of a regexp that never matches anything */ - private boolean neverMatches; - /** Expected token table */ private final Map<Character, Integer> expected = new HashMap<>(); @@ -58,7 +54,7 @@ final class RegExpScanner extends Scanner { private final List<Capture> caps = new LinkedList<>(); /** Forward references to capturing parenthesis to be resolved later.*/ - private final Set<Integer> forwardReferences = new LinkedHashSet<>(); + private final LinkedList<Integer> forwardReferences = new LinkedList<>(); /** Current level of zero-width negative lookahead assertions. */ private int negativeLookaheadLevel; @@ -100,14 +96,17 @@ final class RegExpScanner extends Scanner { } private void processForwardReferences() { - if (neverMatches()) { - return; - } - for (final Integer ref : forwardReferences) { - if (ref.intValue() > caps.size()) { - neverMatches = true; - break; + Iterator<Integer> iterator = forwardReferences.descendingIterator(); + while (iterator.hasNext()) { + final int pos = iterator.next(); + final int num = iterator.next(); + if (num > caps.size()) { + // Non-existing backreference. If the number begins with a valid octal convert it to + // Unicode escape and append the rest to a literal character sequence. + final StringBuilder buffer = new StringBuilder(); + octalOrLiteral(Integer.toString(num), buffer); + sb.insert(pos, buffer); } } @@ -131,9 +130,6 @@ final class RegExpScanner extends Scanner { } scanner.processForwardReferences(); - if (scanner.neverMatches()) { - return null; // never matches - } // Throw syntax error unless we parsed the entire JavaScript regexp without syntax errors if (scanner.position != string.length()) { @@ -142,16 +138,6 @@ final class RegExpScanner extends Scanner { } return scanner; - } - - /** - * Does this regexp ever match anything? Use of e.g. [], which is legal in JavaScript, - * is an example where we never match - * - * @return boolean - */ - private boolean neverMatches() { - return neverMatches; } final StringBuilder getStringBuilder() { @@ -273,23 +259,16 @@ final class RegExpScanner extends Scanner { } if (atom()) { - boolean emptyCharacterClass = false; + // Check for character classes that never or always match if (sb.toString().endsWith("[]")) { - emptyCharacterClass = true; + sb.setLength(sb.length() - 1); + sb.append("^\\s\\S]"); } else if (sb.toString().endsWith("[^]")) { sb.setLength(sb.length() - 2); sb.append("\\s\\S]"); } - boolean quantifier = quantifier(); - - if (emptyCharacterClass) { - if (!quantifier) { - neverMatches = true; //never matches ever. - } - // Note: we could check if quantifier has min zero to mark empty character class as dead. - } - + quantifier(); return true; } @@ -402,6 +381,10 @@ final class RegExpScanner extends Scanner { if (ch0 == '}') { pop('}'); commit(1); + } else { + // Bad quantifier should be rejected but is accepted by all major engines + restart(startIn, startOut); + return false; } return true; @@ -613,13 +596,14 @@ final class RegExpScanner extends Scanner { * ABCDEFGHIJKLMNOPQRSTUVWXYZ */ private boolean controlLetter() { - final char c = Character.toUpperCase(ch0); - if (c >= 'A' && c <= 'Z') { + // To match other engines we also accept '0'..'9' and '_' as control letters inside a character class. + if ((ch0 >= 'A' && ch0 <= 'Z') || (ch0 >= 'a' && ch0 <= 'z') + || (inCharClass && (isDecimalDigit(ch0) || ch0 == '_'))) { // for some reason java regexps don't like control characters on the // form "\\ca".match([string with ascii 1 at char0]). Translating // them to unicode does it though. sb.setLength(sb.length() - 1); - unicode(c - 'A' + 1); + unicode(ch0 % 32, sb); skip(1); return true; } @@ -637,7 +621,9 @@ final class RegExpScanner extends Scanner { throw new RuntimeException("\\ at end of pattern"); // will be converted to PatternSyntaxException } // ES 5.1 A.7 requires "not IdentifierPart" here but all major engines accept any character here. - if (NON_IDENT_ESCAPES.indexOf(ch0) == -1) { + if (ch0 == 'c') { + sb.append('\\'); // Treat invalid \c control sequence as \\c + } else if (NON_IDENT_ESCAPES.indexOf(ch0) == -1) { sb.setLength(sb.length() - 1); } return commit(1); @@ -651,7 +637,7 @@ final class RegExpScanner extends Scanner { final int startIn = position; final int startOut = sb.length(); - if (ch0 == '0' && !isDecimalDigit(ch1)) { + if (ch0 == '0' && !isOctalDigit(ch1)) { skip(1); // DecimalEscape :: 0. If i is zero, return the EscapeValue consisting of a <NUL> character (Unicodevalue0000); sb.append("\u0000"); @@ -659,49 +645,56 @@ final class RegExpScanner extends Scanner { } if (isDecimalDigit(ch0)) { - final int num = ch0 - '0'; - // Single digit escape, treat as backreference. - if (!isDecimalDigit(ch1)) { - if (num <= caps.size() && caps.get(num - 1).getNegativeLookaheadLevel() > 0) { - // Captures that live inside a negative lookahead are dead after the - // lookahead and will be undefined if referenced from outside. - if (caps.get(num - 1).getNegativeLookaheadLevel() > negativeLookaheadLevel) { - sb.setLength(sb.length() - 1); - } else { - sb.append(ch0); + if (ch0 == '0') { + // We know this is an octal escape. + if (inCharClass) { + // Convert octal escape to unicode escape if inside character class. + int octalValue = 0; + while (isOctalDigit(ch0)) { + octalValue = octalValue * 8 + ch0 - '0'; + skip(1); } - skip(1); - return true; - } else if (num > caps.size()) { - // Forward reference to a capture group. Forward references are always undefined so we - // can omit it from the output buffer. Additionally, if the capture group does not exist - // the whole regexp becomes invalid, so register the reference for later processing. - forwardReferences.add(num); - sb.setLength(sb.length() - 1); - skip(1); - return true; - } - } - if (inCharClass) { - // Convert octal escape to unicode escape if inside character class. - StringBuilder digit = new StringBuilder(4); + unicode(octalValue, sb); + + } else { + // Copy decimal escape as-is + decimalDigits(); + } + } else { + // This should be a backreference, but could also be an octal escape or even a literal string. + int decimalValue = 0; while (isDecimalDigit(ch0)) { - digit.append(ch0); + decimalValue = decimalValue * 10 + ch0 - '0'; skip(1); } - int value = Integer.parseInt(digit.toString(), 8); //throws exception that leads to SyntaxError if not octal - if (value > 0xff) { - throw new NumberFormatException(digit.toString()); - } + if (inCharClass) { + // No backreferences in character classes. Encode as unicode escape or literal char sequence + sb.setLength(sb.length() - 1); + octalOrLiteral(Integer.toString(decimalValue), sb); - unicode(value); + } else if (decimalValue <= caps.size() && caps.get(decimalValue - 1).getNegativeLookaheadLevel() > 0) { + // Captures that live inside a negative lookahead are dead after the + // lookahead and will be undefined if referenced from outside. + if (caps.get(decimalValue - 1).getNegativeLookaheadLevel() > negativeLookaheadLevel) { + sb.setLength(sb.length() - 1); + } else { + sb.append(decimalValue); + } + } else if (decimalValue > caps.size()) { + // Forward reference to a capture group. Forward references are always undefined so we can omit + // it from the output buffer. However, if the target capture does not exist, we need to rewrite + // the reference as hex escape or literal string, so register the reference for later processing. + sb.setLength(sb.length() - 1); + forwardReferences.add(decimalValue); + forwardReferences.add(sb.length()); + } else { + // Append as backreference + sb.append(decimalValue); + } - } else { - // Copy decimal escape as-is - decimalDigits(); } return true; } @@ -881,7 +874,6 @@ final class RegExpScanner extends Scanner { switch (ch0) { case ']': case '-': - case '\0': return false; case '[': @@ -942,13 +934,41 @@ final class RegExpScanner extends Scanner { return true; } - private void unicode(final int value) { + private void unicode(final int value, final StringBuilder buffer) { final String hex = Integer.toHexString(value); - sb.append('u'); + buffer.append('u'); for (int i = 0; i < 4 - hex.length(); i++) { - sb.append('0'); + buffer.append('0'); } - sb.append(hex); + buffer.append(hex); + } + + // Convert what would have been a backreference into a unicode escape, or a number literal, or both. + private void octalOrLiteral(final String numberLiteral, final StringBuilder buffer) { + final int length = numberLiteral.length(); + int octalValue = 0; + int pos = 0; + // Maximum value for octal escape is 0377 (255) so we stop the loop at 32 + while (pos < length && octalValue < 0x20) { + final char ch = numberLiteral.charAt(pos); + if (isOctalDigit(ch)) { + octalValue = octalValue * 8 + ch - '0'; + } else { + break; + } + pos++; + } + if (octalValue > 0) { + buffer.append('\\'); + unicode(octalValue, buffer); + buffer.append(numberLiteral.substring(pos)); + } else { + buffer.append(numberLiteral); + } + } + + private static boolean isOctalDigit(final char ch) { + return ch >= '0' && ch <= '7'; } private static boolean isDecimalDigit(final char ch) { diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/Analyser.java b/src/jdk/nashorn/internal/runtime/regexp/joni/Analyser.java index 65d3fcd3..49a2a925 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/Analyser.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/Analyser.java @@ -156,9 +156,6 @@ final class Analyser extends Parser { env.memNodes = null; - new ArrayCompiler(this).compile(); - //new AsmCompiler(this).compile(); - if (regex.numRepeat != 0 || regex.btMemEnd != 0) { regex.stackPopLevel = StackPopLevel.ALL; } else { diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/Parser.java b/src/jdk/nashorn/internal/runtime/regexp/joni/Parser.java index 1b5a5861..13d569b7 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/Parser.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/Parser.java @@ -287,7 +287,10 @@ class Parser extends Lexer { if (syntax.allowDoubleRangeOpInCC()) { env.ccEscWarn("-"); - parseCharClassSbChar(cc, arg); // goto sb_char /* [0-9-a] is allowed as [0-9\-a] */ + arg.inType = CCVALTYPE.SB; + arg.v = '-'; + arg.vIsRaw = false; + parseCharClassValEntry2(cc, arg); // goto val_entry2 /* [0-9-a] is allowed as [0-9\-a] */ break; } newSyntaxException(ERR_UNMATCHED_RANGE_SPECIFIER_IN_CHAR_CLASS); diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/Regex.java b/src/jdk/nashorn/internal/runtime/regexp/joni/Regex.java index ae98cb56..da5b9827 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/Regex.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/Regex.java @@ -55,8 +55,9 @@ public final class Regex implements RegexState { int[]repeatRangeLo; int[]repeatRangeHi; - public WarnCallback warnings; - public MatcherFactory factory; + WarnCallback warnings; + MatcherFactory factory; + private Analyser analyser; int options; int userOptions; @@ -140,19 +141,33 @@ public final class Regex implements RegexState { this.caseFoldFlag = caseFoldFlag; this.warnings = warnings; - new Analyser(new ScanEnvironment(this, syntax), chars, p, end).compile(); + this.analyser = new Analyser(new ScanEnvironment(this, syntax), chars, p, end); + this.analyser.compile(); this.warnings = null; } + public void compile() { + if (factory == null && analyser != null) { + Compiler compiler = new ArrayCompiler(analyser); + analyser = null; // only do this once + compiler.compile(); + } + } + public Matcher matcher(char[] chars) { return matcher(chars, 0, chars.length); } public Matcher matcher(char[] chars, int p, int end) { + compile(); return factory.create(this, chars, p, end); } + public WarnCallback getWarnings() { + return warnings; + } + public int numberOfCaptures() { return numMem; } diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/ast/QuantifierNode.java b/src/jdk/nashorn/internal/runtime/regexp/joni/ast/QuantifierNode.java index 76a5868d..26cde985 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/ast/QuantifierNode.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/ast/QuantifierNode.java @@ -231,12 +231,12 @@ public final class QuantifierNode extends StateNode { break; case DEL: - env.reg.warnings.warn(new String(chars, p, end) + + env.reg.getWarnings().warn(new String(chars, p, end) + " redundant nested repeat operator"); break; default: - env.reg.warnings.warn(new String(chars, p, end) + + env.reg.getWarnings().warn(new String(chars, p, end) + " nested repeat operator " + Reduce.PopularQStr[targetQNum] + " and " + Reduce.PopularQStr[nestQNum] + " was replaced with '" + Reduce.ReduceQStr[Reduce.REDUCE_TABLE[targetQNum][nestQNum].ordinal()] + "'"); diff --git a/src/jdk/nashorn/internal/runtime/resources/Messages.properties b/src/jdk/nashorn/internal/runtime/resources/Messages.properties index 43a7ff62..9f327521 100644 --- a/src/jdk/nashorn/internal/runtime/resources/Messages.properties +++ b/src/jdk/nashorn/internal/runtime/resources/Messages.properties @@ -113,6 +113,7 @@ type.error.cant.convert.to.java.string=Cannot convert object of type {0} to a Ja type.error.cant.convert.to.java.number=Cannot convert object of type {0} to a Java argument of number type type.error.cant.convert.to.javascript.array=Can only convert Java arrays and lists to JavaScript arrays. Can't convert object of type {0}. type.error.extend.expects.at.least.one.argument=Java.extend needs at least one argument. +type.error.extend.expects.at.least.one.type.argument=Java.extend needs at least one type argument. type.error.extend.expects.java.types=Java.extend needs Java types as its arguments. type.error.extend.ambiguous.defining.class=There is no class loader that can see all of {0} at once. type.error.extend.ERROR_FINAL_CLASS=Can not extend final class {0}. diff --git a/src/jdk/nashorn/internal/runtime/resources/Options.properties b/src/jdk/nashorn/internal/runtime/resources/Options.properties index e63f7a37..2b52e8b9 100644 --- a/src/jdk/nashorn/internal/runtime/resources/Options.properties +++ b/src/jdk/nashorn/internal/runtime/resources/Options.properties @@ -89,7 +89,8 @@ nashorn.option.class.cache.size ={ \ short_name="--ccs", \ desc="Size of the Class cache size per global scope.", \ is_undocumented=true, \ - type=Integer \ + type=Integer, \ + default=50 \ } nashorn.option.classpath ={ \ @@ -101,7 +102,7 @@ nashorn.option.classpath ={ \ } nashorn.option.compile.only = { \ - name="--compile-only", \ + name="--compile-only", \ short_name="-co", \ is_undocumented=true, \ desc="Compile without running.", \ @@ -117,10 +118,10 @@ nashorn.option.d = { \ type=String \ } -nashorn.option.doe = { \ - name="-dump-on-error", \ - short_name="-doe", \ - desc="Dump a stack trace on errors."\ +nashorn.option.doe = { \ + name="-dump-on-error", \ + short_name="-doe", \ + desc="Dump a stack trace on errors." \ } nashorn.option.empty.statements = { \ @@ -143,6 +144,26 @@ nashorn.option.fullversion = { \ desc="Print full version info of Nashorn." \ } +nashorn.option.function.statement.error= { \ + name="--function-statement-error", \ + desc="Report an error when function declaration is used as a statement.", \ + is_undocumented=true, \ + default=false \ +} + +nashorn.option.function.statement.warning = { \ + name="--function-statement-warning", \ + desc="Warn when function declaration is used as a statement.", \ + is_undocumented=true, \ + default=false \ +} + +nashorn.option.fx = { \ + name="-fx", \ + desc="Launch script as an fx application.", \ + default=false \ +} + nashorn.option.log = { \ name="--log", \ is_undocumented=true, \ @@ -196,7 +217,7 @@ nashorn.option.package = { \ } nashorn.option.parse.only = { \ - name="--parse-only", \ + name="--parse-only", \ is_undocumented=true, \ desc="Parse without compiling." \ } diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/base.js b/src/jdk/nashorn/internal/runtime/resources/fx/base.js new file mode 100644 index 00000000..00559129 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/base.js @@ -0,0 +1,226 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +Scene = Java.type("javafx.scene.Scene"); +Group = Java.type("javafx.scene.Group"); +Stage = Java.type("javafx.stage.Stage"); + +Binding = Java.type("javafx.beans.binding.Binding"); +Bindings = Java.type("javafx.beans.binding.Bindings"); +BooleanBinding = Java.type("javafx.beans.binding.BooleanBinding"); +BooleanExpression = Java.type("javafx.beans.binding.BooleanExpression"); +DoubleBinding = Java.type("javafx.beans.binding.DoubleBinding"); +DoubleExpression = Java.type("javafx.beans.binding.DoubleExpression"); +FloatBinding = Java.type("javafx.beans.binding.FloatBinding"); +FloatExpression = Java.type("javafx.beans.binding.FloatExpression"); +IntegerBinding = Java.type("javafx.beans.binding.IntegerBinding"); +IntegerExpression = Java.type("javafx.beans.binding.IntegerExpression"); +ListBinding = Java.type("javafx.beans.binding.ListBinding"); +ListExpression = Java.type("javafx.beans.binding.ListExpression"); +LongBinding = Java.type("javafx.beans.binding.LongBinding"); +LongExpression = Java.type("javafx.beans.binding.LongExpression"); +MapBinding = Java.type("javafx.beans.binding.MapBinding"); +MapExpression = Java.type("javafx.beans.binding.MapExpression"); +NumberBinding = Java.type("javafx.beans.binding.NumberBinding"); +NumberExpression = Java.type("javafx.beans.binding.NumberExpression"); +NumberExpressionBase = Java.type("javafx.beans.binding.NumberExpressionBase"); +ObjectBinding = Java.type("javafx.beans.binding.ObjectBinding"); +ObjectExpression = Java.type("javafx.beans.binding.ObjectExpression"); +SetBinding = Java.type("javafx.beans.binding.SetBinding"); +SetExpression = Java.type("javafx.beans.binding.SetExpression"); +StringBinding = Java.type("javafx.beans.binding.StringBinding"); +StringExpression = Java.type("javafx.beans.binding.StringExpression"); +When = Java.type("javafx.beans.binding.When"); +DefaultProperty = Java.type("javafx.beans.DefaultProperty"); +InvalidationListener = Java.type("javafx.beans.InvalidationListener"); +Observable = Java.type("javafx.beans.Observable"); +JavaBeanBooleanProperty = Java.type("javafx.beans.property.adapter.JavaBeanBooleanProperty"); +JavaBeanBooleanPropertyBuilder = Java.type("javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder"); +JavaBeanDoubleProperty = Java.type("javafx.beans.property.adapter.JavaBeanDoubleProperty"); +JavaBeanDoublePropertyBuilder = Java.type("javafx.beans.property.adapter.JavaBeanDoublePropertyBuilder"); +JavaBeanFloatProperty = Java.type("javafx.beans.property.adapter.JavaBeanFloatProperty"); +JavaBeanFloatPropertyBuilder = Java.type("javafx.beans.property.adapter.JavaBeanFloatPropertyBuilder"); +JavaBeanIntegerProperty = Java.type("javafx.beans.property.adapter.JavaBeanIntegerProperty"); +JavaBeanIntegerPropertyBuilder = Java.type("javafx.beans.property.adapter.JavaBeanIntegerPropertyBuilder"); +JavaBeanLongProperty = Java.type("javafx.beans.property.adapter.JavaBeanLongProperty"); +JavaBeanLongPropertyBuilder = Java.type("javafx.beans.property.adapter.JavaBeanLongPropertyBuilder"); +JavaBeanObjectProperty = Java.type("javafx.beans.property.adapter.JavaBeanObjectProperty"); +JavaBeanObjectPropertyBuilder = Java.type("javafx.beans.property.adapter.JavaBeanObjectPropertyBuilder"); +JavaBeanProperty = Java.type("javafx.beans.property.adapter.JavaBeanProperty"); +JavaBeanStringProperty = Java.type("javafx.beans.property.adapter.JavaBeanStringProperty"); +JavaBeanStringPropertyBuilder = Java.type("javafx.beans.property.adapter.JavaBeanStringPropertyBuilder"); +ReadOnlyJavaBeanBooleanProperty = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanBooleanProperty"); +ReadOnlyJavaBeanBooleanPropertyBuilder = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanBooleanPropertyBuilder"); +ReadOnlyJavaBeanDoubleProperty = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanDoubleProperty"); +ReadOnlyJavaBeanDoublePropertyBuilder = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanDoublePropertyBuilder"); +ReadOnlyJavaBeanFloatProperty = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanFloatProperty"); +ReadOnlyJavaBeanFloatPropertyBuilder = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanFloatPropertyBuilder"); +ReadOnlyJavaBeanIntegerProperty = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanIntegerProperty"); +ReadOnlyJavaBeanIntegerPropertyBuilder = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanIntegerPropertyBuilder"); +ReadOnlyJavaBeanLongProperty = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanLongProperty"); +ReadOnlyJavaBeanLongPropertyBuilder = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanLongPropertyBuilder"); +ReadOnlyJavaBeanObjectProperty = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanObjectProperty"); +ReadOnlyJavaBeanObjectPropertyBuilder = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanObjectPropertyBuilder"); +ReadOnlyJavaBeanProperty = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanProperty"); +ReadOnlyJavaBeanStringProperty = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanStringProperty"); +ReadOnlyJavaBeanStringPropertyBuilder = Java.type("javafx.beans.property.adapter.ReadOnlyJavaBeanStringPropertyBuilder"); +BooleanProperty = Java.type("javafx.beans.property.BooleanProperty"); +BooleanPropertyBase = Java.type("javafx.beans.property.BooleanPropertyBase"); +DoubleProperty = Java.type("javafx.beans.property.DoubleProperty"); +DoublePropertyBase = Java.type("javafx.beans.property.DoublePropertyBase"); +FloatProperty = Java.type("javafx.beans.property.FloatProperty"); +FloatPropertyBase = Java.type("javafx.beans.property.FloatPropertyBase"); +IntegerProperty = Java.type("javafx.beans.property.IntegerProperty"); +IntegerPropertyBase = Java.type("javafx.beans.property.IntegerPropertyBase"); +ListProperty = Java.type("javafx.beans.property.ListProperty"); +ListPropertyBase = Java.type("javafx.beans.property.ListPropertyBase"); +LongProperty = Java.type("javafx.beans.property.LongProperty"); +LongPropertyBase = Java.type("javafx.beans.property.LongPropertyBase"); +MapProperty = Java.type("javafx.beans.property.MapProperty"); +MapPropertyBase = Java.type("javafx.beans.property.MapPropertyBase"); +ObjectProperty = Java.type("javafx.beans.property.ObjectProperty"); +ObjectPropertyBase = Java.type("javafx.beans.property.ObjectPropertyBase"); +Property = Java.type("javafx.beans.property.Property"); +ReadOnlyBooleanProperty = Java.type("javafx.beans.property.ReadOnlyBooleanProperty"); +ReadOnlyBooleanPropertyBase = Java.type("javafx.beans.property.ReadOnlyBooleanPropertyBase"); +ReadOnlyBooleanWrapper = Java.type("javafx.beans.property.ReadOnlyBooleanWrapper"); +ReadOnlyDoubleProperty = Java.type("javafx.beans.property.ReadOnlyDoubleProperty"); +ReadOnlyDoublePropertyBase = Java.type("javafx.beans.property.ReadOnlyDoublePropertyBase"); +ReadOnlyDoubleWrapper = Java.type("javafx.beans.property.ReadOnlyDoubleWrapper"); +ReadOnlyFloatProperty = Java.type("javafx.beans.property.ReadOnlyFloatProperty"); +ReadOnlyFloatPropertyBase = Java.type("javafx.beans.property.ReadOnlyFloatPropertyBase"); +ReadOnlyFloatWrapper = Java.type("javafx.beans.property.ReadOnlyFloatWrapper"); +ReadOnlyIntegerProperty = Java.type("javafx.beans.property.ReadOnlyIntegerProperty"); +ReadOnlyIntegerPropertyBase = Java.type("javafx.beans.property.ReadOnlyIntegerPropertyBase"); +ReadOnlyIntegerWrapper = Java.type("javafx.beans.property.ReadOnlyIntegerWrapper"); +ReadOnlyListProperty = Java.type("javafx.beans.property.ReadOnlyListProperty"); +ReadOnlyListPropertyBase = Java.type("javafx.beans.property.ReadOnlyListPropertyBase"); +ReadOnlyListWrapper = Java.type("javafx.beans.property.ReadOnlyListWrapper"); +ReadOnlyLongProperty = Java.type("javafx.beans.property.ReadOnlyLongProperty"); +ReadOnlyLongPropertyBase = Java.type("javafx.beans.property.ReadOnlyLongPropertyBase"); +ReadOnlyLongWrapper = Java.type("javafx.beans.property.ReadOnlyLongWrapper"); +ReadOnlyMapProperty = Java.type("javafx.beans.property.ReadOnlyMapProperty"); +ReadOnlyMapPropertyBase = Java.type("javafx.beans.property.ReadOnlyMapPropertyBase"); +ReadOnlyMapWrapper = Java.type("javafx.beans.property.ReadOnlyMapWrapper"); +ReadOnlyObjectProperty = Java.type("javafx.beans.property.ReadOnlyObjectProperty"); +ReadOnlyObjectPropertyBase = Java.type("javafx.beans.property.ReadOnlyObjectPropertyBase"); +ReadOnlyObjectWrapper = Java.type("javafx.beans.property.ReadOnlyObjectWrapper"); +ReadOnlyProperty = Java.type("javafx.beans.property.ReadOnlyProperty"); +ReadOnlySetProperty = Java.type("javafx.beans.property.ReadOnlySetProperty"); +ReadOnlySetPropertyBase = Java.type("javafx.beans.property.ReadOnlySetPropertyBase"); +ReadOnlySetWrapper = Java.type("javafx.beans.property.ReadOnlySetWrapper"); +ReadOnlyStringProperty = Java.type("javafx.beans.property.ReadOnlyStringProperty"); +ReadOnlyStringPropertyBase = Java.type("javafx.beans.property.ReadOnlyStringPropertyBase"); +ReadOnlyStringWrapper = Java.type("javafx.beans.property.ReadOnlyStringWrapper"); +SetProperty = Java.type("javafx.beans.property.SetProperty"); +SetPropertyBase = Java.type("javafx.beans.property.SetPropertyBase"); +SimpleBooleanProperty = Java.type("javafx.beans.property.SimpleBooleanProperty"); +SimpleDoubleProperty = Java.type("javafx.beans.property.SimpleDoubleProperty"); +SimpleFloatProperty = Java.type("javafx.beans.property.SimpleFloatProperty"); +SimpleIntegerProperty = Java.type("javafx.beans.property.SimpleIntegerProperty"); +SimpleListProperty = Java.type("javafx.beans.property.SimpleListProperty"); +SimpleLongProperty = Java.type("javafx.beans.property.SimpleLongProperty"); +SimpleMapProperty = Java.type("javafx.beans.property.SimpleMapProperty"); +SimpleObjectProperty = Java.type("javafx.beans.property.SimpleObjectProperty"); +SimpleSetProperty = Java.type("javafx.beans.property.SimpleSetProperty"); +SimpleStringProperty = Java.type("javafx.beans.property.SimpleStringProperty"); +StringProperty = Java.type("javafx.beans.property.StringProperty"); +StringPropertyBase = Java.type("javafx.beans.property.StringPropertyBase"); +ChangeListener = Java.type("javafx.beans.value.ChangeListener"); +ObservableBooleanValue = Java.type("javafx.beans.value.ObservableBooleanValue"); +ObservableDoubleValue = Java.type("javafx.beans.value.ObservableDoubleValue"); +ObservableFloatValue = Java.type("javafx.beans.value.ObservableFloatValue"); +ObservableIntegerValue = Java.type("javafx.beans.value.ObservableIntegerValue"); +ObservableListValue = Java.type("javafx.beans.value.ObservableListValue"); +ObservableLongValue = Java.type("javafx.beans.value.ObservableLongValue"); +ObservableMapValue = Java.type("javafx.beans.value.ObservableMapValue"); +ObservableNumberValue = Java.type("javafx.beans.value.ObservableNumberValue"); +ObservableObjectValue = Java.type("javafx.beans.value.ObservableObjectValue"); +ObservableSetValue = Java.type("javafx.beans.value.ObservableSetValue"); +ObservableStringValue = Java.type("javafx.beans.value.ObservableStringValue"); +ObservableValue = Java.type("javafx.beans.value.ObservableValue"); +ObservableValueBase = Java.type("javafx.beans.value.ObservableValueBase"); +WeakChangeListener = Java.type("javafx.beans.value.WeakChangeListener"); +WritableBooleanValue = Java.type("javafx.beans.value.WritableBooleanValue"); +WritableDoubleValue = Java.type("javafx.beans.value.WritableDoubleValue"); +WritableFloatValue = Java.type("javafx.beans.value.WritableFloatValue"); +WritableIntegerValue = Java.type("javafx.beans.value.WritableIntegerValue"); +WritableListValue = Java.type("javafx.beans.value.WritableListValue"); +WritableLongValue = Java.type("javafx.beans.value.WritableLongValue"); +WritableMapValue = Java.type("javafx.beans.value.WritableMapValue"); +WritableNumberValue = Java.type("javafx.beans.value.WritableNumberValue"); +WritableObjectValue = Java.type("javafx.beans.value.WritableObjectValue"); +WritableSetValue = Java.type("javafx.beans.value.WritableSetValue"); +WritableStringValue = Java.type("javafx.beans.value.WritableStringValue"); +WritableValue = Java.type("javafx.beans.value.WritableValue"); +WeakInvalidationListener = Java.type("javafx.beans.WeakInvalidationListener"); +WeakListener = Java.type("javafx.beans.WeakListener"); +FXCollections = Java.type("javafx.collections.FXCollections"); +ListChangeListener = Java.type("javafx.collections.ListChangeListener"); +ListChangeListener$Change = Java.type("javafx.collections.ListChangeListener$Change"); +MapChangeListener = Java.type("javafx.collections.MapChangeListener"); +MapChangeListener$Change = Java.type("javafx.collections.MapChangeListener$Change"); +ModifiableObservableListBase = Java.type("javafx.collections.ModifiableObservableListBase"); +ObservableList = Java.type("javafx.collections.ObservableList"); +ObservableListBase = Java.type("javafx.collections.ObservableListBase"); +ObservableMap = Java.type("javafx.collections.ObservableMap"); +ObservableSet = Java.type("javafx.collections.ObservableSet"); +SetChangeListener = Java.type("javafx.collections.SetChangeListener"); +SetChangeListener$Change = Java.type("javafx.collections.SetChangeListener$Change"); +WeakListChangeListener = Java.type("javafx.collections.WeakListChangeListener"); +WeakMapChangeListener = Java.type("javafx.collections.WeakMapChangeListener"); +WeakSetChangeListener = Java.type("javafx.collections.WeakSetChangeListener"); +ActionEvent = Java.type("javafx.event.ActionEvent"); +Event = Java.type("javafx.event.Event"); +EventDispatchChain = Java.type("javafx.event.EventDispatchChain"); +EventDispatcher = Java.type("javafx.event.EventDispatcher"); +EventHandler = Java.type("javafx.event.EventHandler"); +EventTarget = Java.type("javafx.event.EventTarget"); +EventType = Java.type("javafx.event.EventType"); +WeakEventHandler = Java.type("javafx.event.WeakEventHandler"); +Builder = Java.type("javafx.util.Builder"); +BuilderFactory = Java.type("javafx.util.BuilderFactory"); +Callback = Java.type("javafx.util.Callback"); +BigDecimalStringConverter = Java.type("javafx.util.converter.BigDecimalStringConverter"); +BigIntegerStringConverter = Java.type("javafx.util.converter.BigIntegerStringConverter"); +BooleanStringConverter = Java.type("javafx.util.converter.BooleanStringConverter"); +ByteStringConverter = Java.type("javafx.util.converter.ByteStringConverter"); +CharacterStringConverter = Java.type("javafx.util.converter.CharacterStringConverter"); +CurrencyStringConverter = Java.type("javafx.util.converter.CurrencyStringConverter"); +DateStringConverter = Java.type("javafx.util.converter.DateStringConverter"); +DateTimeStringConverter = Java.type("javafx.util.converter.DateTimeStringConverter"); +DefaultStringConverter = Java.type("javafx.util.converter.DefaultStringConverter"); +DoubleStringConverter = Java.type("javafx.util.converter.DoubleStringConverter"); +FloatStringConverter = Java.type("javafx.util.converter.FloatStringConverter"); +FormatStringConverter = Java.type("javafx.util.converter.FormatStringConverter"); +IntegerStringConverter = Java.type("javafx.util.converter.IntegerStringConverter"); +LongStringConverter = Java.type("javafx.util.converter.LongStringConverter"); +NumberStringConverter = Java.type("javafx.util.converter.NumberStringConverter"); +PercentageStringConverter = Java.type("javafx.util.converter.PercentageStringConverter"); +ShortStringConverter = Java.type("javafx.util.converter.ShortStringConverter"); +TimeStringConverter = Java.type("javafx.util.converter.TimeStringConverter"); +Duration = Java.type("javafx.util.Duration"); +Pair = Java.type("javafx.util.Pair"); +StringConverter = Java.type("javafx.util.StringConverter"); diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/bootstrap.js b/src/jdk/nashorn/internal/runtime/resources/fx/bootstrap.js new file mode 100644 index 00000000..97b01a08 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/bootstrap.js @@ -0,0 +1,74 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +// Check for fx presence. +if (typeof javafx.application.Application != "function") { + print("JavaFX is not available."); + exit(1); +} + +// Extend the javafx.application.Application class overriding init, start and stop. +com.sun.javafx.application.LauncherImpl.launchApplication((Java.extend(javafx.application.Application, { + // Overridden javafx.application.Application.init(); + init: function() { + // Java FX packages and classes must be defined here because + // they may not be viable until launch time due to clinit ordering. + + load("fx:base.js"); + }, + + // Overridden javafx.application.Application.start(Stage stage); + start: function(stage) { + // Set up stage global. + $STAGE = stage; + + // Load user FX scripts. + for each (var script in $SCRIPTS) { + load(script); + } + + // Call the global init function if present. + if ($GLOBAL.init) { + init(); + } + + // Call the global start function if present. Otherwise show the stage. + if ($GLOBAL.start) { + start(stage); + } else { + stage.show(); + } + }, + + // Overridden javafx.application.Application.stop(); + stop: function() { + // Call the global stop function if present. + if ($GLOBAL.stop) { + stop(); + } + } + + // No arguments passed to application (handled thru $ARG.) +})).class, new (Java.type("java.lang.String[]"))(0)); diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/controls.js b/src/jdk/nashorn/internal/runtime/resources/fx/controls.js new file mode 100644 index 00000000..f550107d --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/controls.js @@ -0,0 +1,264 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +AreaChart = Java.type("javafx.scene.chart.AreaChart"); +AreaChartBuilder = Java.type("javafx.scene.chart.AreaChartBuilder"); +Axis = Java.type("javafx.scene.chart.Axis"); +Axis$TickMark = Java.type("javafx.scene.chart.Axis$TickMark"); +AxisBuilder = Java.type("javafx.scene.chart.AxisBuilder"); +BarChart = Java.type("javafx.scene.chart.BarChart"); +BarChartBuilder = Java.type("javafx.scene.chart.BarChartBuilder"); +BubbleChart = Java.type("javafx.scene.chart.BubbleChart"); +BubbleChartBuilder = Java.type("javafx.scene.chart.BubbleChartBuilder"); +CategoryAxis = Java.type("javafx.scene.chart.CategoryAxis"); +CategoryAxisBuilder = Java.type("javafx.scene.chart.CategoryAxisBuilder"); +Chart = Java.type("javafx.scene.chart.Chart"); +ChartBuilder = Java.type("javafx.scene.chart.ChartBuilder"); +LineChart = Java.type("javafx.scene.chart.LineChart"); +LineChartBuilder = Java.type("javafx.scene.chart.LineChartBuilder"); +NumberAxis = Java.type("javafx.scene.chart.NumberAxis"); +NumberAxis$DefaultFormatter = Java.type("javafx.scene.chart.NumberAxis$DefaultFormatter"); +NumberAxisBuilder = Java.type("javafx.scene.chart.NumberAxisBuilder"); +PieChart = Java.type("javafx.scene.chart.PieChart"); +PieChart$Data = Java.type("javafx.scene.chart.PieChart$Data"); +PieChartBuilder = Java.type("javafx.scene.chart.PieChartBuilder"); +ScatterChart = Java.type("javafx.scene.chart.ScatterChart"); +ScatterChartBuilder = Java.type("javafx.scene.chart.ScatterChartBuilder"); +StackedAreaChart = Java.type("javafx.scene.chart.StackedAreaChart"); +StackedAreaChartBuilder = Java.type("javafx.scene.chart.StackedAreaChartBuilder"); +StackedBarChart = Java.type("javafx.scene.chart.StackedBarChart"); +StackedBarChartBuilder = Java.type("javafx.scene.chart.StackedBarChartBuilder"); +ValueAxis = Java.type("javafx.scene.chart.ValueAxis"); +ValueAxisBuilder = Java.type("javafx.scene.chart.ValueAxisBuilder"); +XYChart = Java.type("javafx.scene.chart.XYChart"); +XYChart$Data = Java.type("javafx.scene.chart.XYChart$Data"); +XYChart$Series = Java.type("javafx.scene.chart.XYChart$Series"); +XYChartBuilder = Java.type("javafx.scene.chart.XYChartBuilder"); +Accordion = Java.type("javafx.scene.control.Accordion"); +AccordionBuilder = Java.type("javafx.scene.control.AccordionBuilder"); +Button = Java.type("javafx.scene.control.Button"); +ButtonBase = Java.type("javafx.scene.control.ButtonBase"); +ButtonBaseBuilder = Java.type("javafx.scene.control.ButtonBaseBuilder"); +ButtonBuilder = Java.type("javafx.scene.control.ButtonBuilder"); +Cell = Java.type("javafx.scene.control.Cell"); +CheckBoxListCell = Java.type("javafx.scene.control.cell.CheckBoxListCell"); +CheckBoxListCellBuilder = Java.type("javafx.scene.control.cell.CheckBoxListCellBuilder"); +CheckBoxTableCell = Java.type("javafx.scene.control.cell.CheckBoxTableCell"); +CheckBoxTableCellBuilder = Java.type("javafx.scene.control.cell.CheckBoxTableCellBuilder"); +CheckBoxTreeCell = Java.type("javafx.scene.control.cell.CheckBoxTreeCell"); +CheckBoxTreeCellBuilder = Java.type("javafx.scene.control.cell.CheckBoxTreeCellBuilder"); +CheckBoxTreeTableCell = Java.type("javafx.scene.control.cell.CheckBoxTreeTableCell"); +CheckBoxTreeTableCellBuilder = Java.type("javafx.scene.control.cell.CheckBoxTreeTableCellBuilder"); +ChoiceBoxListCell = Java.type("javafx.scene.control.cell.ChoiceBoxListCell"); +ChoiceBoxListCellBuilder = Java.type("javafx.scene.control.cell.ChoiceBoxListCellBuilder"); +ChoiceBoxTableCell = Java.type("javafx.scene.control.cell.ChoiceBoxTableCell"); +ChoiceBoxTableCellBuilder = Java.type("javafx.scene.control.cell.ChoiceBoxTableCellBuilder"); +ChoiceBoxTreeCell = Java.type("javafx.scene.control.cell.ChoiceBoxTreeCell"); +ChoiceBoxTreeCellBuilder = Java.type("javafx.scene.control.cell.ChoiceBoxTreeCellBuilder"); +ChoiceBoxTreeTableCell = Java.type("javafx.scene.control.cell.ChoiceBoxTreeTableCell"); +ChoiceBoxTreeTableCellBuilder = Java.type("javafx.scene.control.cell.ChoiceBoxTreeTableCellBuilder"); +ComboBoxListCell = Java.type("javafx.scene.control.cell.ComboBoxListCell"); +ComboBoxListCellBuilder = Java.type("javafx.scene.control.cell.ComboBoxListCellBuilder"); +ComboBoxTableCell = Java.type("javafx.scene.control.cell.ComboBoxTableCell"); +ComboBoxTableCellBuilder = Java.type("javafx.scene.control.cell.ComboBoxTableCellBuilder"); +ComboBoxTreeCell = Java.type("javafx.scene.control.cell.ComboBoxTreeCell"); +ComboBoxTreeCellBuilder = Java.type("javafx.scene.control.cell.ComboBoxTreeCellBuilder"); +ComboBoxTreeTableCell = Java.type("javafx.scene.control.cell.ComboBoxTreeTableCell"); +ComboBoxTreeTableCellBuilder = Java.type("javafx.scene.control.cell.ComboBoxTreeTableCellBuilder"); +MapValueFactory = Java.type("javafx.scene.control.cell.MapValueFactory"); +ProgressBarTableCell = Java.type("javafx.scene.control.cell.ProgressBarTableCell"); +ProgressBarTreeTableCell = Java.type("javafx.scene.control.cell.ProgressBarTreeTableCell"); +PropertyValueFactory = Java.type("javafx.scene.control.cell.PropertyValueFactory"); +PropertyValueFactoryBuilder = Java.type("javafx.scene.control.cell.PropertyValueFactoryBuilder"); +TextFieldListCell = Java.type("javafx.scene.control.cell.TextFieldListCell"); +TextFieldListCellBuilder = Java.type("javafx.scene.control.cell.TextFieldListCellBuilder"); +TextFieldTableCell = Java.type("javafx.scene.control.cell.TextFieldTableCell"); +TextFieldTableCellBuilder = Java.type("javafx.scene.control.cell.TextFieldTableCellBuilder"); +TextFieldTreeCell = Java.type("javafx.scene.control.cell.TextFieldTreeCell"); +TextFieldTreeCellBuilder = Java.type("javafx.scene.control.cell.TextFieldTreeCellBuilder"); +TextFieldTreeTableCell = Java.type("javafx.scene.control.cell.TextFieldTreeTableCell"); +TextFieldTreeTableCellBuilder = Java.type("javafx.scene.control.cell.TextFieldTreeTableCellBuilder"); +TreeItemPropertyValueFactory = Java.type("javafx.scene.control.cell.TreeItemPropertyValueFactory"); +TreeItemPropertyValueFactoryBuilder = Java.type("javafx.scene.control.cell.TreeItemPropertyValueFactoryBuilder"); +CellBuilder = Java.type("javafx.scene.control.CellBuilder"); +CheckBox = Java.type("javafx.scene.control.CheckBox"); +CheckBoxBuilder = Java.type("javafx.scene.control.CheckBoxBuilder"); +CheckBoxTreeItem = Java.type("javafx.scene.control.CheckBoxTreeItem"); +CheckBoxTreeItem$TreeModificationEvent = Java.type("javafx.scene.control.CheckBoxTreeItem$TreeModificationEvent"); +CheckBoxTreeItemBuilder = Java.type("javafx.scene.control.CheckBoxTreeItemBuilder"); +CheckMenuItem = Java.type("javafx.scene.control.CheckMenuItem"); +CheckMenuItemBuilder = Java.type("javafx.scene.control.CheckMenuItemBuilder"); +ChoiceBox = Java.type("javafx.scene.control.ChoiceBox"); +ChoiceBoxBuilder = Java.type("javafx.scene.control.ChoiceBoxBuilder"); +ColorPicker = Java.type("javafx.scene.control.ColorPicker"); +ColorPickerBuilder = Java.type("javafx.scene.control.ColorPickerBuilder"); +ComboBox = Java.type("javafx.scene.control.ComboBox"); +ComboBoxBase = Java.type("javafx.scene.control.ComboBoxBase"); +ComboBoxBaseBuilder = Java.type("javafx.scene.control.ComboBoxBaseBuilder"); +ComboBoxBuilder = Java.type("javafx.scene.control.ComboBoxBuilder"); +ContentDisplay = Java.type("javafx.scene.control.ContentDisplay"); +ContextMenu = Java.type("javafx.scene.control.ContextMenu"); +ContextMenuBuilder = Java.type("javafx.scene.control.ContextMenuBuilder"); +Control = Java.type("javafx.scene.control.Control"); +ControlBuilder = Java.type("javafx.scene.control.ControlBuilder"); +CustomMenuItem = Java.type("javafx.scene.control.CustomMenuItem"); +CustomMenuItemBuilder = Java.type("javafx.scene.control.CustomMenuItemBuilder"); +FocusModel = Java.type("javafx.scene.control.FocusModel"); +Hyperlink = Java.type("javafx.scene.control.Hyperlink"); +HyperlinkBuilder = Java.type("javafx.scene.control.HyperlinkBuilder"); +IndexedCell = Java.type("javafx.scene.control.IndexedCell"); +IndexedCellBuilder = Java.type("javafx.scene.control.IndexedCellBuilder"); +IndexRange = Java.type("javafx.scene.control.IndexRange"); +IndexRangeBuilder = Java.type("javafx.scene.control.IndexRangeBuilder"); +Label = Java.type("javafx.scene.control.Label"); +LabelBuilder = Java.type("javafx.scene.control.LabelBuilder"); +Labeled = Java.type("javafx.scene.control.Labeled"); +LabeledBuilder = Java.type("javafx.scene.control.LabeledBuilder"); +ListCell = Java.type("javafx.scene.control.ListCell"); +ListCellBuilder = Java.type("javafx.scene.control.ListCellBuilder"); +ListView = Java.type("javafx.scene.control.ListView"); +ListView$EditEvent = Java.type("javafx.scene.control.ListView$EditEvent"); +ListViewBuilder = Java.type("javafx.scene.control.ListViewBuilder"); +Menu = Java.type("javafx.scene.control.Menu"); +MenuBar = Java.type("javafx.scene.control.MenuBar"); +MenuBarBuilder = Java.type("javafx.scene.control.MenuBarBuilder"); +MenuBuilder = Java.type("javafx.scene.control.MenuBuilder"); +MenuButton = Java.type("javafx.scene.control.MenuButton"); +MenuButtonBuilder = Java.type("javafx.scene.control.MenuButtonBuilder"); +MenuItem = Java.type("javafx.scene.control.MenuItem"); +MenuItemBuilder = Java.type("javafx.scene.control.MenuItemBuilder"); +MultipleSelectionModel = Java.type("javafx.scene.control.MultipleSelectionModel"); +MultipleSelectionModelBuilder = Java.type("javafx.scene.control.MultipleSelectionModelBuilder"); +OverrunStyle = Java.type("javafx.scene.control.OverrunStyle"); +Pagination = Java.type("javafx.scene.control.Pagination"); +PaginationBuilder = Java.type("javafx.scene.control.PaginationBuilder"); +PasswordField = Java.type("javafx.scene.control.PasswordField"); +PasswordFieldBuilder = Java.type("javafx.scene.control.PasswordFieldBuilder"); +PopupControl = Java.type("javafx.scene.control.PopupControl"); +PopupControlBuilder = Java.type("javafx.scene.control.PopupControlBuilder"); +ProgressBar = Java.type("javafx.scene.control.ProgressBar"); +ProgressBarBuilder = Java.type("javafx.scene.control.ProgressBarBuilder"); +ProgressIndicator = Java.type("javafx.scene.control.ProgressIndicator"); +ProgressIndicatorBuilder = Java.type("javafx.scene.control.ProgressIndicatorBuilder"); +RadioButton = Java.type("javafx.scene.control.RadioButton"); +RadioButtonBuilder = Java.type("javafx.scene.control.RadioButtonBuilder"); +RadioMenuItem = Java.type("javafx.scene.control.RadioMenuItem"); +RadioMenuItemBuilder = Java.type("javafx.scene.control.RadioMenuItemBuilder"); +ResizeFeaturesBase = Java.type("javafx.scene.control.ResizeFeaturesBase"); +ResizeFeaturesBaseBuilder = Java.type("javafx.scene.control.ResizeFeaturesBaseBuilder"); +ScrollBar = Java.type("javafx.scene.control.ScrollBar"); +ScrollBarBuilder = Java.type("javafx.scene.control.ScrollBarBuilder"); +ScrollPane = Java.type("javafx.scene.control.ScrollPane"); +ScrollPane$ScrollBarPolicy = Java.type("javafx.scene.control.ScrollPane$ScrollBarPolicy"); +ScrollPaneBuilder = Java.type("javafx.scene.control.ScrollPaneBuilder"); +ScrollToEvent = Java.type("javafx.scene.control.ScrollToEvent"); +SelectionMode = Java.type("javafx.scene.control.SelectionMode"); +SelectionModel = Java.type("javafx.scene.control.SelectionModel"); +Separator = Java.type("javafx.scene.control.Separator"); +SeparatorBuilder = Java.type("javafx.scene.control.SeparatorBuilder"); +SeparatorMenuItem = Java.type("javafx.scene.control.SeparatorMenuItem"); +SeparatorMenuItemBuilder = Java.type("javafx.scene.control.SeparatorMenuItemBuilder"); +SingleSelectionModel = Java.type("javafx.scene.control.SingleSelectionModel"); +Skin = Java.type("javafx.scene.control.Skin"); +SkinBase = Java.type("javafx.scene.control.SkinBase"); +SkinBaseBuilder = Java.type("javafx.scene.control.SkinBaseBuilder"); +Skinnable = Java.type("javafx.scene.control.Skinnable"); +Slider = Java.type("javafx.scene.control.Slider"); +SliderBuilder = Java.type("javafx.scene.control.SliderBuilder"); +SortEvent = Java.type("javafx.scene.control.SortEvent"); +SplitMenuButton = Java.type("javafx.scene.control.SplitMenuButton"); +SplitMenuButtonBuilder = Java.type("javafx.scene.control.SplitMenuButtonBuilder"); +SplitPane = Java.type("javafx.scene.control.SplitPane"); +SplitPane$Divider = Java.type("javafx.scene.control.SplitPane$Divider"); +SplitPaneBuilder = Java.type("javafx.scene.control.SplitPaneBuilder"); +Tab = Java.type("javafx.scene.control.Tab"); +TabBuilder = Java.type("javafx.scene.control.TabBuilder"); +TableCell = Java.type("javafx.scene.control.TableCell"); +TableCellBuilder = Java.type("javafx.scene.control.TableCellBuilder"); +TableColumn = Java.type("javafx.scene.control.TableColumn"); +TableColumn$CellDataFeatures = Java.type("javafx.scene.control.TableColumn$CellDataFeatures"); +TableColumn$CellEditEvent = Java.type("javafx.scene.control.TableColumn$CellEditEvent"); +TableColumn$SortType = Java.type("javafx.scene.control.TableColumn$SortType"); +TableColumnBase = Java.type("javafx.scene.control.TableColumnBase"); +TableColumnBaseBuilder = Java.type("javafx.scene.control.TableColumnBaseBuilder"); +TableColumnBuilder = Java.type("javafx.scene.control.TableColumnBuilder"); +TableFocusModel = Java.type("javafx.scene.control.TableFocusModel"); +TablePosition = Java.type("javafx.scene.control.TablePosition"); +TablePositionBase = Java.type("javafx.scene.control.TablePositionBase"); +TableRow = Java.type("javafx.scene.control.TableRow"); +TableRowBuilder = Java.type("javafx.scene.control.TableRowBuilder"); +TableSelectionModel = Java.type("javafx.scene.control.TableSelectionModel"); +TableSelectionModelBuilder = Java.type("javafx.scene.control.TableSelectionModelBuilder"); +TableView = Java.type("javafx.scene.control.TableView"); +TableView$ResizeFeatures = Java.type("javafx.scene.control.TableView$ResizeFeatures"); +TableView$TableViewFocusModel = Java.type("javafx.scene.control.TableView$TableViewFocusModel"); +TableView$TableViewSelectionModel = Java.type("javafx.scene.control.TableView$TableViewSelectionModel"); +TableViewBuilder = Java.type("javafx.scene.control.TableViewBuilder"); +TabPane = Java.type("javafx.scene.control.TabPane"); +TabPane$TabClosingPolicy = Java.type("javafx.scene.control.TabPane$TabClosingPolicy"); +TabPaneBuilder = Java.type("javafx.scene.control.TabPaneBuilder"); +TextArea = Java.type("javafx.scene.control.TextArea"); +TextAreaBuilder = Java.type("javafx.scene.control.TextAreaBuilder"); +TextField = Java.type("javafx.scene.control.TextField"); +TextFieldBuilder = Java.type("javafx.scene.control.TextFieldBuilder"); +TextInputControl = Java.type("javafx.scene.control.TextInputControl"); +TextInputControl$Content = Java.type("javafx.scene.control.TextInputControl$Content"); +TextInputControlBuilder = Java.type("javafx.scene.control.TextInputControlBuilder"); +TitledPane = Java.type("javafx.scene.control.TitledPane"); +TitledPaneBuilder = Java.type("javafx.scene.control.TitledPaneBuilder"); +Toggle = Java.type("javafx.scene.control.Toggle"); +ToggleButton = Java.type("javafx.scene.control.ToggleButton"); +ToggleButtonBuilder = Java.type("javafx.scene.control.ToggleButtonBuilder"); +ToggleGroup = Java.type("javafx.scene.control.ToggleGroup"); +ToggleGroupBuilder = Java.type("javafx.scene.control.ToggleGroupBuilder"); +ToolBar = Java.type("javafx.scene.control.ToolBar"); +ToolBarBuilder = Java.type("javafx.scene.control.ToolBarBuilder"); +Tooltip = Java.type("javafx.scene.control.Tooltip"); +TooltipBuilder = Java.type("javafx.scene.control.TooltipBuilder"); +TreeCell = Java.type("javafx.scene.control.TreeCell"); +TreeCellBuilder = Java.type("javafx.scene.control.TreeCellBuilder"); +TreeItem = Java.type("javafx.scene.control.TreeItem"); +TreeItem$TreeModificationEvent = Java.type("javafx.scene.control.TreeItem$TreeModificationEvent"); +TreeItemBuilder = Java.type("javafx.scene.control.TreeItemBuilder"); +TreeSortMode = Java.type("javafx.scene.control.TreeSortMode"); +TreeTableCell = Java.type("javafx.scene.control.TreeTableCell"); +TreeTableCellBuilder = Java.type("javafx.scene.control.TreeTableCellBuilder"); +TreeTableColumn = Java.type("javafx.scene.control.TreeTableColumn"); +TreeTableColumn$CellDataFeatures = Java.type("javafx.scene.control.TreeTableColumn$CellDataFeatures"); +TreeTableColumn$CellEditEvent = Java.type("javafx.scene.control.TreeTableColumn$CellEditEvent"); +TreeTableColumn$SortType = Java.type("javafx.scene.control.TreeTableColumn$SortType"); +TreeTableColumnBuilder = Java.type("javafx.scene.control.TreeTableColumnBuilder"); +TreeTablePosition = Java.type("javafx.scene.control.TreeTablePosition"); +TreeTableRow = Java.type("javafx.scene.control.TreeTableRow"); +TreeTableRowBuilder = Java.type("javafx.scene.control.TreeTableRowBuilder"); +TreeTableView = Java.type("javafx.scene.control.TreeTableView"); +TreeTableView$EditEvent = Java.type("javafx.scene.control.TreeTableView$EditEvent"); +TreeTableView$ResizeFeatures = Java.type("javafx.scene.control.TreeTableView$ResizeFeatures"); +TreeTableView$TreeTableViewFocusModel = Java.type("javafx.scene.control.TreeTableView$TreeTableViewFocusModel"); +TreeTableView$TreeTableViewSelectionModel = Java.type("javafx.scene.control.TreeTableView$TreeTableViewSelectionModel"); +TreeTableViewBuilder = Java.type("javafx.scene.control.TreeTableViewBuilder"); +TreeView = Java.type("javafx.scene.control.TreeView"); +TreeView$EditEvent = Java.type("javafx.scene.control.TreeView$EditEvent"); +TreeViewBuilder = Java.type("javafx.scene.control.TreeViewBuilder"); diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/fxml.js b/src/jdk/nashorn/internal/runtime/resources/fx/fxml.js new file mode 100644 index 00000000..bc018a9e --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/fxml.js @@ -0,0 +1,30 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +FXML = Java.type("javafx.fxml.FXML"); +FXMLLoader = Java.type("javafx.fxml.FXMLLoader"); +Initializable = Java.type("javafx.fxml.Initializable"); +JavaFXBuilderFactory = Java.type("javafx.fxml.JavaFXBuilderFactory"); +LoadException = Java.type("javafx.fxml.LoadException"); diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/graphics.js b/src/jdk/nashorn/internal/runtime/resources/fx/graphics.js new file mode 100644 index 00000000..9da00bd2 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/graphics.js @@ -0,0 +1,431 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +Animation = Java.type("javafx.animation.Animation"); +Animation$Status = Java.type("javafx.animation.Animation$Status"); +AnimationBuilder = Java.type("javafx.animation.AnimationBuilder"); +AnimationTimer = Java.type("javafx.animation.AnimationTimer"); +FadeTransition = Java.type("javafx.animation.FadeTransition"); +FadeTransitionBuilder = Java.type("javafx.animation.FadeTransitionBuilder"); +FillTransition = Java.type("javafx.animation.FillTransition"); +FillTransitionBuilder = Java.type("javafx.animation.FillTransitionBuilder"); +Interpolatable = Java.type("javafx.animation.Interpolatable"); +Interpolator = Java.type("javafx.animation.Interpolator"); +KeyFrame = Java.type("javafx.animation.KeyFrame"); +KeyValue = Java.type("javafx.animation.KeyValue"); +ParallelTransition = Java.type("javafx.animation.ParallelTransition"); +ParallelTransitionBuilder = Java.type("javafx.animation.ParallelTransitionBuilder"); +PathTransition = Java.type("javafx.animation.PathTransition"); +PathTransition$OrientationType = Java.type("javafx.animation.PathTransition$OrientationType"); +PathTransitionBuilder = Java.type("javafx.animation.PathTransitionBuilder"); +PauseTransition = Java.type("javafx.animation.PauseTransition"); +PauseTransitionBuilder = Java.type("javafx.animation.PauseTransitionBuilder"); +RotateTransition = Java.type("javafx.animation.RotateTransition"); +RotateTransitionBuilder = Java.type("javafx.animation.RotateTransitionBuilder"); +ScaleTransition = Java.type("javafx.animation.ScaleTransition"); +ScaleTransitionBuilder = Java.type("javafx.animation.ScaleTransitionBuilder"); +SequentialTransition = Java.type("javafx.animation.SequentialTransition"); +SequentialTransitionBuilder = Java.type("javafx.animation.SequentialTransitionBuilder"); +StrokeTransition = Java.type("javafx.animation.StrokeTransition"); +StrokeTransitionBuilder = Java.type("javafx.animation.StrokeTransitionBuilder"); +Timeline = Java.type("javafx.animation.Timeline"); +TimelineBuilder = Java.type("javafx.animation.TimelineBuilder"); +Transition = Java.type("javafx.animation.Transition"); +TransitionBuilder = Java.type("javafx.animation.TransitionBuilder"); +TranslateTransition = Java.type("javafx.animation.TranslateTransition"); +TranslateTransitionBuilder = Java.type("javafx.animation.TranslateTransitionBuilder"); +Application = Java.type("javafx.application.Application"); +Application$Parameters = Java.type("javafx.application.Application$Parameters"); +ConditionalFeature = Java.type("javafx.application.ConditionalFeature"); +HostServices = Java.type("javafx.application.HostServices"); +Platform = Java.type("javafx.application.Platform"); +Preloader = Java.type("javafx.application.Preloader"); +Preloader$ErrorNotification = Java.type("javafx.application.Preloader$ErrorNotification"); +Preloader$PreloaderNotification = Java.type("javafx.application.Preloader$PreloaderNotification"); +Preloader$ProgressNotification = Java.type("javafx.application.Preloader$ProgressNotification"); +Preloader$StateChangeNotification = Java.type("javafx.application.Preloader$StateChangeNotification"); +Preloader$StateChangeNotification$Type = Java.type("javafx.application.Preloader$StateChangeNotification$Type"); +ScheduledService = Java.type("javafx.concurrent.ScheduledService"); +Service = Java.type("javafx.concurrent.Service"); +Task = Java.type("javafx.concurrent.Task"); +Worker = Java.type("javafx.concurrent.Worker"); +Worker$State = Java.type("javafx.concurrent.Worker$State"); +WorkerStateEvent = Java.type("javafx.concurrent.WorkerStateEvent"); +CssMetaData = Java.type("javafx.css.CssMetaData"); +FontCssMetaData = Java.type("javafx.css.FontCssMetaData"); +ParsedValue = Java.type("javafx.css.ParsedValue"); +PseudoClass = Java.type("javafx.css.PseudoClass"); +SimpleStyleableBooleanProperty = Java.type("javafx.css.SimpleStyleableBooleanProperty"); +SimpleStyleableDoubleProperty = Java.type("javafx.css.SimpleStyleableDoubleProperty"); +SimpleStyleableFloatProperty = Java.type("javafx.css.SimpleStyleableFloatProperty"); +SimpleStyleableIntegerProperty = Java.type("javafx.css.SimpleStyleableIntegerProperty"); +SimpleStyleableLongProperty = Java.type("javafx.css.SimpleStyleableLongProperty"); +SimpleStyleableObjectProperty = Java.type("javafx.css.SimpleStyleableObjectProperty"); +SimpleStyleableStringProperty = Java.type("javafx.css.SimpleStyleableStringProperty"); +Styleable = Java.type("javafx.css.Styleable"); +StyleableBooleanProperty = Java.type("javafx.css.StyleableBooleanProperty"); +StyleableDoubleProperty = Java.type("javafx.css.StyleableDoubleProperty"); +StyleableFloatProperty = Java.type("javafx.css.StyleableFloatProperty"); +StyleableIntegerProperty = Java.type("javafx.css.StyleableIntegerProperty"); +StyleableLongProperty = Java.type("javafx.css.StyleableLongProperty"); +StyleableObjectProperty = Java.type("javafx.css.StyleableObjectProperty"); +StyleableProperty = Java.type("javafx.css.StyleableProperty"); +StyleableStringProperty = Java.type("javafx.css.StyleableStringProperty"); +StyleConverter = Java.type("javafx.css.StyleConverter"); +StyleOrigin = Java.type("javafx.css.StyleOrigin"); +BoundingBox = Java.type("javafx.geometry.BoundingBox"); +BoundingBoxBuilder = Java.type("javafx.geometry.BoundingBoxBuilder"); +Bounds = Java.type("javafx.geometry.Bounds"); +Dimension2D = Java.type("javafx.geometry.Dimension2D"); +Dimension2DBuilder = Java.type("javafx.geometry.Dimension2DBuilder"); +HorizontalDirection = Java.type("javafx.geometry.HorizontalDirection"); +HPos = Java.type("javafx.geometry.HPos"); +Insets = Java.type("javafx.geometry.Insets"); +InsetsBuilder = Java.type("javafx.geometry.InsetsBuilder"); +NodeOrientation = Java.type("javafx.geometry.NodeOrientation"); +Orientation = Java.type("javafx.geometry.Orientation"); +Point2D = Java.type("javafx.geometry.Point2D"); +Point2DBuilder = Java.type("javafx.geometry.Point2DBuilder"); +Point3D = Java.type("javafx.geometry.Point3D"); +Point3DBuilder = Java.type("javafx.geometry.Point3DBuilder"); +Pos = Java.type("javafx.geometry.Pos"); +Rectangle2D = Java.type("javafx.geometry.Rectangle2D"); +Rectangle2DBuilder = Java.type("javafx.geometry.Rectangle2DBuilder"); +Side = Java.type("javafx.geometry.Side"); +VerticalDirection = Java.type("javafx.geometry.VerticalDirection"); +VPos = Java.type("javafx.geometry.VPos"); +Collation = Java.type("javafx.print.Collation"); +JobSettings = Java.type("javafx.print.JobSettings"); +PageLayout = Java.type("javafx.print.PageLayout"); +PageOrientation = Java.type("javafx.print.PageOrientation"); +PageRange = Java.type("javafx.print.PageRange"); +Paper = Java.type("javafx.print.Paper"); +Paper$Units = Java.type("javafx.print.Paper$Units"); +PaperSource = Java.type("javafx.print.PaperSource"); +PrintColor = Java.type("javafx.print.PrintColor"); +Printer = Java.type("javafx.print.Printer"); +Printer$MarginType = Java.type("javafx.print.Printer$MarginType"); +PrinterAttributes = Java.type("javafx.print.PrinterAttributes"); +PrinterJob = Java.type("javafx.print.PrinterJob"); +PrinterJob$JobStatus = Java.type("javafx.print.PrinterJob$JobStatus"); +PrintQuality = Java.type("javafx.print.PrintQuality"); +PrintResolution = Java.type("javafx.print.PrintResolution"); +PrintSides = Java.type("javafx.print.PrintSides"); +AmbientLight = Java.type("javafx.scene.AmbientLight"); +AmbientLightBuilder = Java.type("javafx.scene.AmbientLightBuilder"); +CacheHint = Java.type("javafx.scene.CacheHint"); +Camera = Java.type("javafx.scene.Camera"); +CameraBuilder = Java.type("javafx.scene.CameraBuilder"); +Canvas = Java.type("javafx.scene.canvas.Canvas"); +CanvasBuilder = Java.type("javafx.scene.canvas.CanvasBuilder"); +GraphicsContext = Java.type("javafx.scene.canvas.GraphicsContext"); +Cursor = Java.type("javafx.scene.Cursor"); +DepthTest = Java.type("javafx.scene.DepthTest"); +Blend = Java.type("javafx.scene.effect.Blend"); +BlendBuilder = Java.type("javafx.scene.effect.BlendBuilder"); +BlendMode = Java.type("javafx.scene.effect.BlendMode"); +Bloom = Java.type("javafx.scene.effect.Bloom"); +BloomBuilder = Java.type("javafx.scene.effect.BloomBuilder"); +BlurType = Java.type("javafx.scene.effect.BlurType"); +BoxBlur = Java.type("javafx.scene.effect.BoxBlur"); +BoxBlurBuilder = Java.type("javafx.scene.effect.BoxBlurBuilder"); +ColorAdjust = Java.type("javafx.scene.effect.ColorAdjust"); +ColorAdjustBuilder = Java.type("javafx.scene.effect.ColorAdjustBuilder"); +ColorInput = Java.type("javafx.scene.effect.ColorInput"); +ColorInputBuilder = Java.type("javafx.scene.effect.ColorInputBuilder"); +DisplacementMap = Java.type("javafx.scene.effect.DisplacementMap"); +DisplacementMapBuilder = Java.type("javafx.scene.effect.DisplacementMapBuilder"); +DropShadow = Java.type("javafx.scene.effect.DropShadow"); +DropShadowBuilder = Java.type("javafx.scene.effect.DropShadowBuilder"); +Effect = Java.type("javafx.scene.effect.Effect"); +FloatMap = Java.type("javafx.scene.effect.FloatMap"); +FloatMapBuilder = Java.type("javafx.scene.effect.FloatMapBuilder"); +GaussianBlur = Java.type("javafx.scene.effect.GaussianBlur"); +GaussianBlurBuilder = Java.type("javafx.scene.effect.GaussianBlurBuilder"); +Glow = Java.type("javafx.scene.effect.Glow"); +GlowBuilder = Java.type("javafx.scene.effect.GlowBuilder"); +ImageInput = Java.type("javafx.scene.effect.ImageInput"); +ImageInputBuilder = Java.type("javafx.scene.effect.ImageInputBuilder"); +InnerShadow = Java.type("javafx.scene.effect.InnerShadow"); +InnerShadowBuilder = Java.type("javafx.scene.effect.InnerShadowBuilder"); +Light = Java.type("javafx.scene.effect.Light"); +Light$Distant = Java.type("javafx.scene.effect.Light$Distant"); +Light$Point = Java.type("javafx.scene.effect.Light$Point"); +Light$Spot = Java.type("javafx.scene.effect.Light$Spot"); +LightBuilder = Java.type("javafx.scene.effect.LightBuilder"); +Lighting = Java.type("javafx.scene.effect.Lighting"); +LightingBuilder = Java.type("javafx.scene.effect.LightingBuilder"); +MotionBlur = Java.type("javafx.scene.effect.MotionBlur"); +MotionBlurBuilder = Java.type("javafx.scene.effect.MotionBlurBuilder"); +PerspectiveTransform = Java.type("javafx.scene.effect.PerspectiveTransform"); +PerspectiveTransformBuilder = Java.type("javafx.scene.effect.PerspectiveTransformBuilder"); +Reflection = Java.type("javafx.scene.effect.Reflection"); +ReflectionBuilder = Java.type("javafx.scene.effect.ReflectionBuilder"); +SepiaTone = Java.type("javafx.scene.effect.SepiaTone"); +SepiaToneBuilder = Java.type("javafx.scene.effect.SepiaToneBuilder"); +Shadow = Java.type("javafx.scene.effect.Shadow"); +ShadowBuilder = Java.type("javafx.scene.effect.ShadowBuilder"); +//Group = Java.type("javafx.scene.Group"); +GroupBuilder = Java.type("javafx.scene.GroupBuilder"); +Image = Java.type("javafx.scene.image.Image"); +ImageView = Java.type("javafx.scene.image.ImageView"); +ImageViewBuilder = Java.type("javafx.scene.image.ImageViewBuilder"); +PixelFormat = Java.type("javafx.scene.image.PixelFormat"); +PixelFormat$Type = Java.type("javafx.scene.image.PixelFormat$Type"); +PixelReader = Java.type("javafx.scene.image.PixelReader"); +PixelWriter = Java.type("javafx.scene.image.PixelWriter"); +WritableImage = Java.type("javafx.scene.image.WritableImage"); +WritablePixelFormat = Java.type("javafx.scene.image.WritablePixelFormat"); +ImageCursor = Java.type("javafx.scene.ImageCursor"); +ImageCursorBuilder = Java.type("javafx.scene.ImageCursorBuilder"); +Clipboard = Java.type("javafx.scene.input.Clipboard"); +ClipboardContent = Java.type("javafx.scene.input.ClipboardContent"); +ClipboardContentBuilder = Java.type("javafx.scene.input.ClipboardContentBuilder"); +ContextMenuEvent = Java.type("javafx.scene.input.ContextMenuEvent"); +DataFormat = Java.type("javafx.scene.input.DataFormat"); +Dragboard = Java.type("javafx.scene.input.Dragboard"); +DragEvent = Java.type("javafx.scene.input.DragEvent"); +GestureEvent = Java.type("javafx.scene.input.GestureEvent"); +InputEvent = Java.type("javafx.scene.input.InputEvent"); +InputEventBuilder = Java.type("javafx.scene.input.InputEventBuilder"); +InputMethodEvent = Java.type("javafx.scene.input.InputMethodEvent"); +InputMethodHighlight = Java.type("javafx.scene.input.InputMethodHighlight"); +InputMethodRequests = Java.type("javafx.scene.input.InputMethodRequests"); +InputMethodTextRun = Java.type("javafx.scene.input.InputMethodTextRun"); +InputMethodTextRunBuilder = Java.type("javafx.scene.input.InputMethodTextRunBuilder"); +KeyCharacterCombination = Java.type("javafx.scene.input.KeyCharacterCombination"); +KeyCharacterCombinationBuilder = Java.type("javafx.scene.input.KeyCharacterCombinationBuilder"); +KeyCode = Java.type("javafx.scene.input.KeyCode"); +KeyCodeCombination = Java.type("javafx.scene.input.KeyCodeCombination"); +KeyCodeCombinationBuilder = Java.type("javafx.scene.input.KeyCodeCombinationBuilder"); +KeyCombination = Java.type("javafx.scene.input.KeyCombination"); +KeyCombination$Modifier = Java.type("javafx.scene.input.KeyCombination$Modifier"); +KeyCombination$ModifierValue = Java.type("javafx.scene.input.KeyCombination$ModifierValue"); +KeyEvent = Java.type("javafx.scene.input.KeyEvent"); +Mnemonic = Java.type("javafx.scene.input.Mnemonic"); +MnemonicBuilder = Java.type("javafx.scene.input.MnemonicBuilder"); +MouseButton = Java.type("javafx.scene.input.MouseButton"); +MouseDragEvent = Java.type("javafx.scene.input.MouseDragEvent"); +MouseEvent = Java.type("javafx.scene.input.MouseEvent"); +PickResult = Java.type("javafx.scene.input.PickResult"); +RotateEvent = Java.type("javafx.scene.input.RotateEvent"); +ScrollEvent = Java.type("javafx.scene.input.ScrollEvent"); +ScrollEvent$HorizontalTextScrollUnits = Java.type("javafx.scene.input.ScrollEvent$HorizontalTextScrollUnits"); +ScrollEvent$VerticalTextScrollUnits = Java.type("javafx.scene.input.ScrollEvent$VerticalTextScrollUnits"); +SwipeEvent = Java.type("javafx.scene.input.SwipeEvent"); +TouchEvent = Java.type("javafx.scene.input.TouchEvent"); +TouchPoint = Java.type("javafx.scene.input.TouchPoint"); +TouchPoint$State = Java.type("javafx.scene.input.TouchPoint$State"); +TouchPointBuilder = Java.type("javafx.scene.input.TouchPointBuilder"); +TransferMode = Java.type("javafx.scene.input.TransferMode"); +ZoomEvent = Java.type("javafx.scene.input.ZoomEvent"); +AnchorPane = Java.type("javafx.scene.layout.AnchorPane"); +AnchorPaneBuilder = Java.type("javafx.scene.layout.AnchorPaneBuilder"); +Background = Java.type("javafx.scene.layout.Background"); +BackgroundBuilder = Java.type("javafx.scene.layout.BackgroundBuilder"); +BackgroundFill = Java.type("javafx.scene.layout.BackgroundFill"); +BackgroundFillBuilder = Java.type("javafx.scene.layout.BackgroundFillBuilder"); +BackgroundImage = Java.type("javafx.scene.layout.BackgroundImage"); +BackgroundImageBuilder = Java.type("javafx.scene.layout.BackgroundImageBuilder"); +BackgroundPosition = Java.type("javafx.scene.layout.BackgroundPosition"); +BackgroundPositionBuilder = Java.type("javafx.scene.layout.BackgroundPositionBuilder"); +BackgroundRepeat = Java.type("javafx.scene.layout.BackgroundRepeat"); +BackgroundSize = Java.type("javafx.scene.layout.BackgroundSize"); +BackgroundSizeBuilder = Java.type("javafx.scene.layout.BackgroundSizeBuilder"); +Border = Java.type("javafx.scene.layout.Border"); +BorderBuilder = Java.type("javafx.scene.layout.BorderBuilder"); +BorderImage = Java.type("javafx.scene.layout.BorderImage"); +BorderImageBuilder = Java.type("javafx.scene.layout.BorderImageBuilder"); +BorderPane = Java.type("javafx.scene.layout.BorderPane"); +BorderPaneBuilder = Java.type("javafx.scene.layout.BorderPaneBuilder"); +BorderRepeat = Java.type("javafx.scene.layout.BorderRepeat"); +BorderStroke = Java.type("javafx.scene.layout.BorderStroke"); +BorderStrokeBuilder = Java.type("javafx.scene.layout.BorderStrokeBuilder"); +BorderStrokeStyle = Java.type("javafx.scene.layout.BorderStrokeStyle"); +BorderStrokeStyleBuilder = Java.type("javafx.scene.layout.BorderStrokeStyleBuilder"); +BorderWidths = Java.type("javafx.scene.layout.BorderWidths"); +BorderWidthsBuilder = Java.type("javafx.scene.layout.BorderWidthsBuilder"); +ColumnConstraints = Java.type("javafx.scene.layout.ColumnConstraints"); +ColumnConstraintsBuilder = Java.type("javafx.scene.layout.ColumnConstraintsBuilder"); +ConstraintsBase = Java.type("javafx.scene.layout.ConstraintsBase"); +CornerRadii = Java.type("javafx.scene.layout.CornerRadii"); +FlowPane = Java.type("javafx.scene.layout.FlowPane"); +FlowPaneBuilder = Java.type("javafx.scene.layout.FlowPaneBuilder"); +GridPane = Java.type("javafx.scene.layout.GridPane"); +GridPaneBuilder = Java.type("javafx.scene.layout.GridPaneBuilder"); +HBox = Java.type("javafx.scene.layout.HBox"); +HBoxBuilder = Java.type("javafx.scene.layout.HBoxBuilder"); +Pane = Java.type("javafx.scene.layout.Pane"); +PaneBuilder = Java.type("javafx.scene.layout.PaneBuilder"); +Priority = Java.type("javafx.scene.layout.Priority"); +Region = Java.type("javafx.scene.layout.Region"); +RegionBuilder = Java.type("javafx.scene.layout.RegionBuilder"); +RowConstraints = Java.type("javafx.scene.layout.RowConstraints"); +RowConstraintsBuilder = Java.type("javafx.scene.layout.RowConstraintsBuilder"); +StackPane = Java.type("javafx.scene.layout.StackPane"); +StackPaneBuilder = Java.type("javafx.scene.layout.StackPaneBuilder"); +TilePane = Java.type("javafx.scene.layout.TilePane"); +TilePaneBuilder = Java.type("javafx.scene.layout.TilePaneBuilder"); +VBox = Java.type("javafx.scene.layout.VBox"); +VBoxBuilder = Java.type("javafx.scene.layout.VBoxBuilder"); +LightBase = Java.type("javafx.scene.LightBase"); +LightBaseBuilder = Java.type("javafx.scene.LightBaseBuilder"); +Node = Java.type("javafx.scene.Node"); +NodeBuilder = Java.type("javafx.scene.NodeBuilder"); +Color = Java.type("javafx.scene.paint.Color"); +ColorBuilder = Java.type("javafx.scene.paint.ColorBuilder"); +CycleMethod = Java.type("javafx.scene.paint.CycleMethod"); +ImagePattern = Java.type("javafx.scene.paint.ImagePattern"); +ImagePatternBuilder = Java.type("javafx.scene.paint.ImagePatternBuilder"); +LinearGradient = Java.type("javafx.scene.paint.LinearGradient"); +LinearGradientBuilder = Java.type("javafx.scene.paint.LinearGradientBuilder"); +Material = Java.type("javafx.scene.paint.Material"); +Paint = Java.type("javafx.scene.paint.Paint"); +PhongMaterial = Java.type("javafx.scene.paint.PhongMaterial"); +PhongMaterialBuilder = Java.type("javafx.scene.paint.PhongMaterialBuilder"); +RadialGradient = Java.type("javafx.scene.paint.RadialGradient"); +RadialGradientBuilder = Java.type("javafx.scene.paint.RadialGradientBuilder"); +Stop = Java.type("javafx.scene.paint.Stop"); +StopBuilder = Java.type("javafx.scene.paint.StopBuilder"); +ParallelCamera = Java.type("javafx.scene.ParallelCamera"); +ParallelCameraBuilder = Java.type("javafx.scene.ParallelCameraBuilder"); +Parent = Java.type("javafx.scene.Parent"); +ParentBuilder = Java.type("javafx.scene.ParentBuilder"); +PerspectiveCamera = Java.type("javafx.scene.PerspectiveCamera"); +PerspectiveCameraBuilder = Java.type("javafx.scene.PerspectiveCameraBuilder"); +PointLight = Java.type("javafx.scene.PointLight"); +PointLightBuilder = Java.type("javafx.scene.PointLightBuilder"); +//Scene = Java.type("javafx.scene.Scene"); +SceneBuilder = Java.type("javafx.scene.SceneBuilder"); +Arc = Java.type("javafx.scene.shape.Arc"); +ArcBuilder = Java.type("javafx.scene.shape.ArcBuilder"); +ArcTo = Java.type("javafx.scene.shape.ArcTo"); +ArcToBuilder = Java.type("javafx.scene.shape.ArcToBuilder"); +ArcType = Java.type("javafx.scene.shape.ArcType"); +Box = Java.type("javafx.scene.shape.Box"); +BoxBuilder = Java.type("javafx.scene.shape.BoxBuilder"); +Circle = Java.type("javafx.scene.shape.Circle"); +CircleBuilder = Java.type("javafx.scene.shape.CircleBuilder"); +ClosePath = Java.type("javafx.scene.shape.ClosePath"); +ClosePathBuilder = Java.type("javafx.scene.shape.ClosePathBuilder"); +CubicCurve = Java.type("javafx.scene.shape.CubicCurve"); +CubicCurveBuilder = Java.type("javafx.scene.shape.CubicCurveBuilder"); +CubicCurveTo = Java.type("javafx.scene.shape.CubicCurveTo"); +CubicCurveToBuilder = Java.type("javafx.scene.shape.CubicCurveToBuilder"); +CullFace = Java.type("javafx.scene.shape.CullFace"); +Cylinder = Java.type("javafx.scene.shape.Cylinder"); +CylinderBuilder = Java.type("javafx.scene.shape.CylinderBuilder"); +DrawMode = Java.type("javafx.scene.shape.DrawMode"); +Ellipse = Java.type("javafx.scene.shape.Ellipse"); +EllipseBuilder = Java.type("javafx.scene.shape.EllipseBuilder"); +FillRule = Java.type("javafx.scene.shape.FillRule"); +HLineTo = Java.type("javafx.scene.shape.HLineTo"); +HLineToBuilder = Java.type("javafx.scene.shape.HLineToBuilder"); +Line = Java.type("javafx.scene.shape.Line"); +LineBuilder = Java.type("javafx.scene.shape.LineBuilder"); +LineTo = Java.type("javafx.scene.shape.LineTo"); +LineToBuilder = Java.type("javafx.scene.shape.LineToBuilder"); +Mesh = Java.type("javafx.scene.shape.Mesh"); +MeshView = Java.type("javafx.scene.shape.MeshView"); +MeshViewBuilder = Java.type("javafx.scene.shape.MeshViewBuilder"); +MoveTo = Java.type("javafx.scene.shape.MoveTo"); +MoveToBuilder = Java.type("javafx.scene.shape.MoveToBuilder"); +Path = Java.type("javafx.scene.shape.Path"); +PathBuilder = Java.type("javafx.scene.shape.PathBuilder"); +PathElement = Java.type("javafx.scene.shape.PathElement"); +PathElementBuilder = Java.type("javafx.scene.shape.PathElementBuilder"); +Polygon = Java.type("javafx.scene.shape.Polygon"); +PolygonBuilder = Java.type("javafx.scene.shape.PolygonBuilder"); +Polyline = Java.type("javafx.scene.shape.Polyline"); +PolylineBuilder = Java.type("javafx.scene.shape.PolylineBuilder"); +QuadCurve = Java.type("javafx.scene.shape.QuadCurve"); +QuadCurveBuilder = Java.type("javafx.scene.shape.QuadCurveBuilder"); +QuadCurveTo = Java.type("javafx.scene.shape.QuadCurveTo"); +QuadCurveToBuilder = Java.type("javafx.scene.shape.QuadCurveToBuilder"); +Rectangle = Java.type("javafx.scene.shape.Rectangle"); +RectangleBuilder = Java.type("javafx.scene.shape.RectangleBuilder"); +Shape = Java.type("javafx.scene.shape.Shape"); +Shape3D = Java.type("javafx.scene.shape.Shape3D"); +Shape3DBuilder = Java.type("javafx.scene.shape.Shape3DBuilder"); +ShapeBuilder = Java.type("javafx.scene.shape.ShapeBuilder"); +Sphere = Java.type("javafx.scene.shape.Sphere"); +SphereBuilder = Java.type("javafx.scene.shape.SphereBuilder"); +StrokeLineCap = Java.type("javafx.scene.shape.StrokeLineCap"); +StrokeLineJoin = Java.type("javafx.scene.shape.StrokeLineJoin"); +StrokeType = Java.type("javafx.scene.shape.StrokeType"); +SVGPath = Java.type("javafx.scene.shape.SVGPath"); +SVGPathBuilder = Java.type("javafx.scene.shape.SVGPathBuilder"); +TriangleMesh = Java.type("javafx.scene.shape.TriangleMesh"); +VLineTo = Java.type("javafx.scene.shape.VLineTo"); +VLineToBuilder = Java.type("javafx.scene.shape.VLineToBuilder"); +SnapshotParameters = Java.type("javafx.scene.SnapshotParameters"); +SnapshotParametersBuilder = Java.type("javafx.scene.SnapshotParametersBuilder"); +SnapshotResult = Java.type("javafx.scene.SnapshotResult"); +SubScene = Java.type("javafx.scene.SubScene"); +SubSceneBuilder = Java.type("javafx.scene.SubSceneBuilder"); +Font = Java.type("javafx.scene.text.Font"); +FontBuilder = Java.type("javafx.scene.text.FontBuilder"); +FontPosture = Java.type("javafx.scene.text.FontPosture"); +FontSmoothingType = Java.type("javafx.scene.text.FontSmoothingType"); +FontWeight = Java.type("javafx.scene.text.FontWeight"); +Text = Java.type("javafx.scene.text.Text"); +TextAlignment = Java.type("javafx.scene.text.TextAlignment"); +TextBoundsType = Java.type("javafx.scene.text.TextBoundsType"); +TextBuilder = Java.type("javafx.scene.text.TextBuilder"); +TextFlow = Java.type("javafx.scene.text.TextFlow"); +TextFlowBuilder = Java.type("javafx.scene.text.TextFlowBuilder"); +Affine = Java.type("javafx.scene.transform.Affine"); +AffineBuilder = Java.type("javafx.scene.transform.AffineBuilder"); +MatrixType = Java.type("javafx.scene.transform.MatrixType"); +NonInvertibleTransformException = Java.type("javafx.scene.transform.NonInvertibleTransformException"); +Rotate = Java.type("javafx.scene.transform.Rotate"); +RotateBuilder = Java.type("javafx.scene.transform.RotateBuilder"); +Scale = Java.type("javafx.scene.transform.Scale"); +ScaleBuilder = Java.type("javafx.scene.transform.ScaleBuilder"); +Shear = Java.type("javafx.scene.transform.Shear"); +ShearBuilder = Java.type("javafx.scene.transform.ShearBuilder"); +Transform = Java.type("javafx.scene.transform.Transform"); +TransformBuilder = Java.type("javafx.scene.transform.TransformBuilder"); +TransformChangedEvent = Java.type("javafx.scene.transform.TransformChangedEvent"); +Translate = Java.type("javafx.scene.transform.Translate"); +TranslateBuilder = Java.type("javafx.scene.transform.TranslateBuilder"); +DirectoryChooser = Java.type("javafx.stage.DirectoryChooser"); +DirectoryChooserBuilder = Java.type("javafx.stage.DirectoryChooserBuilder"); +FileChooser = Java.type("javafx.stage.FileChooser"); +FileChooser$ExtensionFilter = Java.type("javafx.stage.FileChooser$ExtensionFilter"); +FileChooserBuilder = Java.type("javafx.stage.FileChooserBuilder"); +Modality = Java.type("javafx.stage.Modality"); +Popup = Java.type("javafx.stage.Popup"); +PopupBuilder = Java.type("javafx.stage.PopupBuilder"); +PopupWindow = Java.type("javafx.stage.PopupWindow"); +PopupWindowBuilder = Java.type("javafx.stage.PopupWindowBuilder"); +Screen = Java.type("javafx.stage.Screen"); +//Stage = Java.type("javafx.stage.Stage"); +StageBuilder = Java.type("javafx.stage.StageBuilder"); +StageStyle = Java.type("javafx.stage.StageStyle"); +Window = Java.type("javafx.stage.Window"); +WindowBuilder = Java.type("javafx.stage.WindowBuilder"); +WindowEvent = Java.type("javafx.stage.WindowEvent"); + diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/media.js b/src/jdk/nashorn/internal/runtime/resources/fx/media.js new file mode 100644 index 00000000..7cc7887b --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/media.js @@ -0,0 +1,45 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +AudioClip = Java.type("javafx.scene.media.AudioClip"); +AudioClipBuilder = Java.type("javafx.scene.media.AudioClipBuilder"); +AudioEqualizer = Java.type("javafx.scene.media.AudioEqualizer"); +AudioSpectrumListener = Java.type("javafx.scene.media.AudioSpectrumListener"); +AudioTrack = Java.type("javafx.scene.media.AudioTrack"); +EqualizerBand = Java.type("javafx.scene.media.EqualizerBand"); +Media = Java.type("javafx.scene.media.Media"); +MediaBuilder = Java.type("javafx.scene.media.MediaBuilder"); +MediaErrorEvent = Java.type("javafx.scene.media.MediaErrorEvent"); +MediaException = Java.type("javafx.scene.media.MediaException"); +MediaException$Type = Java.type("javafx.scene.media.MediaException$Type"); +MediaMarkerEvent = Java.type("javafx.scene.media.MediaMarkerEvent"); +MediaPlayer = Java.type("javafx.scene.media.MediaPlayer"); +MediaPlayer$Status = Java.type("javafx.scene.media.MediaPlayer$Status"); +MediaPlayerBuilder = Java.type("javafx.scene.media.MediaPlayerBuilder"); +MediaView = Java.type("javafx.scene.media.MediaView"); +MediaViewBuilder = Java.type("javafx.scene.media.MediaViewBuilder"); +SubtitleTrack = Java.type("javafx.scene.media.SubtitleTrack"); +Track = Java.type("javafx.scene.media.Track"); +VideoTrack = Java.type("javafx.scene.media.VideoTrack"); diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/swing.js b/src/jdk/nashorn/internal/runtime/resources/fx/swing.js new file mode 100644 index 00000000..241205d1 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/swing.js @@ -0,0 +1,29 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +JFXPanel = Java.type("javafx.embed.swing.JFXPanel"); +JFXPanelBuilder = Java.type("javafx.embed.swing.JFXPanelBuilder"); +SwingFXUtils = Java.type("javafx.embed.swing.SwingFXUtils"); +SwingNode = Java.type("javafx.embed.swing.SwingNode"); diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/swt.js b/src/jdk/nashorn/internal/runtime/resources/fx/swt.js new file mode 100644 index 00000000..526be750 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/swt.js @@ -0,0 +1,29 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +CustomTransfer = Java.type("javafx.embed.swt.CustomTransfer"); +CustomTransferBuilder = Java.type("javafx.embed.swt.CustomTransferBuilder"); +FXCanvas = Java.type("javafx.embed.swt.FXCanvas"); +SWTFXUtils = Java.type("javafx.embed.swt.SWTFXUtils"); diff --git a/src/jdk/nashorn/internal/runtime/resources/fx/web.js b/src/jdk/nashorn/internal/runtime/resources/fx/web.js new file mode 100644 index 00000000..d9099345 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/resources/fx/web.js @@ -0,0 +1,36 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +HTMLEditor = Java.type("javafx.scene.web.HTMLEditor"); +HTMLEditorBuilder = Java.type("javafx.scene.web.HTMLEditorBuilder"); +PopupFeatures = Java.type("javafx.scene.web.PopupFeatures"); +PromptData = Java.type("javafx.scene.web.PromptData"); +PromptDataBuilder = Java.type("javafx.scene.web.PromptDataBuilder"); +WebEngine = Java.type("javafx.scene.web.WebEngine"); +WebEngineBuilder = Java.type("javafx.scene.web.WebEngineBuilder"); +WebEvent = Java.type("javafx.scene.web.WebEvent"); +WebHistory = Java.type("javafx.scene.web.WebHistory"); +WebView = Java.type("javafx.scene.web.WebView"); +WebViewBuilder = Java.type("javafx.scene.web.WebViewBuilder"); diff --git a/src/jdk/nashorn/internal/runtime/resources/mozilla_compat.js b/src/jdk/nashorn/internal/runtime/resources/mozilla_compat.js index 0b967d69..206a193f 100644 --- a/src/jdk/nashorn/internal/runtime/resources/mozilla_compat.js +++ b/src/jdk/nashorn/internal/runtime/resources/mozilla_compat.js @@ -49,6 +49,7 @@ Object.defineProperty(this, "importPackage", { var global = this; var oldNoSuchProperty = global.__noSuchProperty__; global.__noSuchProperty__ = function(name) { + 'use strict'; for (var i in _packages) { try { var type = Java.type(_packages[i] + "." + name); @@ -57,7 +58,15 @@ Object.defineProperty(this, "importPackage", { } catch (e) {} } - return oldNoSuchProperty? oldNoSuchProperty(name) : undefined; + if (oldNoSuchProperty) { + return oldNoSuchProperty.call(this, name); + } else { + if (this === undefined) { + throw new ReferenceError(name + " is not defined"); + } else { + return undefined; + } + } } var prefix = "[JavaPackage "; diff --git a/src/jdk/nashorn/tools/Shell.java b/src/jdk/nashorn/tools/Shell.java index 0c040a2c..22d879f5 100644 --- a/src/jdk/nashorn/tools/Shell.java +++ b/src/jdk/nashorn/tools/Shell.java @@ -42,9 +42,12 @@ import java.util.ResourceBundle; import jdk.nashorn.api.scripting.NashornException; import jdk.nashorn.internal.codegen.Compiler; import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.debug.ASTWriter; +import jdk.nashorn.internal.ir.debug.PrintVisitor; import jdk.nashorn.internal.parser.Parser; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.ErrorManager; +import jdk.nashorn.internal.runtime.Property; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; @@ -168,7 +171,11 @@ public class Shell { return compileScripts(context, global, files); } - return runScripts(context, global, files); + if (env._fx) { + return runFXScripts(context, global, files); + } else { + return runScripts(context, global, files); + } } /** @@ -254,6 +261,14 @@ public class Shell { return COMPILATION_ERROR; } + if (env._print_ast) { + context.getErr().println(new ASTWriter(functionNode)); + } + + if (env._print_parse) { + context.getErr().println(new PrintVisitor(functionNode)); + } + //null - pass no code installer - this is compile only new Compiler(env, functionNode).compile(); } @@ -318,6 +333,46 @@ public class Shell { } /** + * Runs launches "fx:bootstrap.js" with the given JavaScript files provided + * as arguments. + * + * @param context the nashorn context + * @param global the global scope + * @param files the list of script files to provide + * + * @return error code + * @throws IOException when any script file read results in I/O error + */ + private int runFXScripts(final Context context, final ScriptObject global, final List<String> files) throws IOException { + final ScriptObject oldGlobal = Context.getGlobal(); + final boolean globalChanged = (oldGlobal != global); + try { + if (globalChanged) { + Context.setGlobal(global); + } + + global.addOwnProperty("$GLOBAL", Property.NOT_ENUMERABLE, global); + global.addOwnProperty("$SCRIPTS", Property.NOT_ENUMERABLE, files); + context.load(global, "fx:bootstrap.js"); + } catch (final NashornException e) { + context.getErrorManager().error(e.toString()); + if (context.getEnv()._dump_on_error) { + e.printStackTrace(context.getErr()); + } + + return RUNTIME_ERROR; + } finally { + context.getOut().flush(); + context.getErr().flush(); + if (globalChanged) { + Context.setGlobal(oldGlobal); + } + } + + return SUCCESS; + } + + /** * Hook to ScriptFunction "apply". A performance metering shell may * introduce enter/exit timing here. * diff --git a/test/examples/int-micro.js b/test/examples/int-micro.js new file mode 100644 index 00000000..82f4b099 --- /dev/null +++ b/test/examples/int-micro.js @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +function bench(name, func) { + var start = Date.now(); + for (var iter = 0; iter < 5000000; iter++) { + func(); + } + print(name + "\t" + (Date.now() - start)); +} + +function uint32(value) { + return function() { + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + value >>> 0; + }; +} + +function int32(value) { + return function() { + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + value >> 0; + }; +} + +print("\nToUint32"); +for (var i = 1; i < 3; i++) { + bench("infinity ", uint32(Infinity)); + bench("infinity neg ", uint32(-Infinity)); + bench("nan ", uint32(NaN)); + bench("small ", uint32(1)); + bench("small neg ", uint32(-1)); + bench("small frac ", uint32(1.5)); + bench("small neg frac", uint32(-1.5)); + bench("large ", uint32(9223372036854775807)); + bench("large neg ", uint32(-9223372036854775808)); +} + +print("\nToInt32"); +for (var i = 1; i < 3; i++) { + bench("infinity ", int32(Infinity)); + bench("infinity neg ", int32(-Infinity)); + bench("nan ", int32(NaN)); + bench("small ", int32(1)); + bench("small neg ", int32(-1)); + bench("small frac ", int32(1.5)); + bench("small neg frac", int32(-1.5)); + bench("large ", int32(9223372036854775807)); + bench("large neg ", int32(-9223372036854775808)); +} + + diff --git a/test/script/basic/JDK-8008238.js b/test/script/basic/JDK-8008238.js new file mode 100644 index 00000000..f58ec20a --- /dev/null +++ b/test/script/basic/JDK-8008238.js @@ -0,0 +1,34 @@ +/* + * 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-8008238: Labeled break in finally causes stack overflow in Node copy + * + * @test + * @run + */ + +a: try { +} finally { + break a; +} diff --git a/test/script/basic/JDK-8008814-3.js b/test/script/basic/JDK-8008814-3.js new file mode 100644 index 00000000..87a9ebf4 --- /dev/null +++ b/test/script/basic/JDK-8008814-3.js @@ -0,0 +1,40 @@ +/* + * 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. + */ + +/** + * NASHORN-8008814: it's not a compile time error to have a nested strict function declaration when the outer one is not strict + * + * @test + * @run + */ + +function f() { + if(true) { + function g() { + "use strict"; + print("g invoked!") + } + } + g() +} +f() diff --git a/test/script/basic/JDK-8008814-3.js.EXPECTED b/test/script/basic/JDK-8008814-3.js.EXPECTED new file mode 100644 index 00000000..dab29578 --- /dev/null +++ b/test/script/basic/JDK-8008814-3.js.EXPECTED @@ -0,0 +1 @@ +g invoked! diff --git a/test/script/basic/JDK-8008814-4.js b/test/script/basic/JDK-8008814-4.js new file mode 100644 index 00000000..baca0221 --- /dev/null +++ b/test/script/basic/JDK-8008814-4.js @@ -0,0 +1,40 @@ +/* + * 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. + */ + +/** + * NASHORN-8008814: it's not a compile time error to have a nested function declaration when warnings are reported + * + * @option --function-statement-warning + * @test + * @run/ignore-std-error + */ + +function f() { + if(true) { + function g() { + print("g invoked!") + } + } + g() +} +f() diff --git a/test/script/basic/JDK-8008814-4.js.EXPECTED b/test/script/basic/JDK-8008814-4.js.EXPECTED new file mode 100644 index 00000000..dab29578 --- /dev/null +++ b/test/script/basic/JDK-8008814-4.js.EXPECTED @@ -0,0 +1 @@ +g invoked! diff --git a/test/script/basic/JDK-8009230.js b/test/script/basic/JDK-8009230.js new file mode 100644 index 00000000..f161158a --- /dev/null +++ b/test/script/basic/JDK-8009230.js @@ -0,0 +1,93 @@ +/* + * 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-8009230: Nashorn rejects extended RegExp syntax accepted by all major JS engines + * + * @test + * @run + */ + + +// Invalid ControlEscape/IdentityEscape character treated as literal. +print(/\z/.exec("z")); // Invalid escape, same as /z/ +// Incomplete/Invalid ControlEscape treated as "\\c" +print(/\c/.exec("\\c")); // same as /\\c/ +print(/\c2/.exec("\\c2")); // same as /\\c2/ +print(/\C/.exec("C")); // same as /C/ +print(/\C2/.exec("C2")); // same as /C2/ +// Incomplete HexEscapeSequence escape treated as "x". +print(/\x/.exec("x")); // incomplete x-escape +print(/\x1/.exec("x1")); // incomplete x-escape +print(/\x1z/.exec("x1z")); // incomplete x-escape +// Incomplete UnicodeEscapeSequence escape treated as "u". +print(/\u/.exec("u")); // incomplete u-escape +print(/\uz/.exec("uz")); // incomplete u-escape +print(/\u1/.exec("u1")); // incomplete u-escape +print(/\u1z/.exec("u1z")); // incomplete u-escape +print(/\u12/.exec("u12")); // incomplete u-escape +print(/\u12z/.exec("u12z")); // incomplete u-escape +print(/\u123/.exec("u123")); // incomplete u-escape +print(/\u123z/.exec("u123z")); // incomplete u-escape +// Bad quantifier range: +print(/x{z/.exec("x{z")); // same as /x\{z/ +print(/x{1z/.exec("x{1z")); // same as /x\{1z/ +print(/x{1,z/.exec("x{1,z")); // same as /x\{1,z/ +print(/x{1,2z/.exec("x{1,2z")); // same as /x\{1,2z/ +print(/x{10000,20000z/.exec("x{10000,20000z")); // same as /x\{10000,20000z/ +// Notice: It needs arbitrary lookahead to determine the invalidity, +// except Mozilla that limits the numbers. + +// Zero-initialized Octal escapes. +/\012/; // same as /\x0a/ + +// Nonexisting back-references smaller than 8 treated as octal escapes: +print(/\5/.exec("\u0005")); // same as /\x05/ +print(/\7/.exec("\u0007")); // same as /\x07/ +print(/\8/.exec("\u0008")); // does not match + +// Invalid PatternCharacter accepted unescaped +print(/]/.exec("]")); +print(/{/.exec("{")); +print(/}/.exec("}")); + +// Bad escapes also inside CharacterClass. +print(/[\z]/.exec("z")); +print(/[\c]/.exec("c")); +print(/[\c2]/.exec("c")); +print(/[\x]/.exec("x")); +print(/[\x1]/.exec("x1")); +print(/[\x1z]/.exec("x1z")); +print(/[\u]/.exec("u")); +print(/[\uz]/.exec("u")); +print(/[\u1]/.exec("u")); +print(/[\u1z]/.exec("u")); +print(/[\u12]/.exec("u")); +print(/[\u12z]/.exec("u")); +print(/[\u123]/.exec("u")); +print(/[\u123z]/.exec("u")); +print(/[\012]/.exec("0")); +print(/[\5]/.exec("5")); +// And in addition: +print(/[\B]/.exec("B")); +print(/()()[\2]/.exec("")); // Valid backreference should be invalid. diff --git a/test/script/basic/JDK-8009230.js.EXPECTED b/test/script/basic/JDK-8009230.js.EXPECTED new file mode 100644 index 00000000..63f6e615 --- /dev/null +++ b/test/script/basic/JDK-8009230.js.EXPECTED @@ -0,0 +1,45 @@ +z +\c +\c2 +C +C2 +x +x1 +x1z +u +uz +u1 +u1z +u12 +u12z +u123 +u123z +x{z +x{1z +x{1,z +x{1,2z +x{10000,20000z + + +null +] +{ +} +z +c +null +x +x +x +u +u +u +u +u +u +u +u +null +null +B +null diff --git a/test/script/basic/JDK-8017010.js b/test/script/basic/JDK-8010710.js index aa6e61a9..aa6e61a9 100644 --- a/test/script/basic/JDK-8017010.js +++ b/test/script/basic/JDK-8010710.js diff --git a/test/script/basic/JDK-8017010.js.EXPECTED b/test/script/basic/JDK-8010710.js.EXPECTED index 296c81e5..296c81e5 100644 --- a/test/script/basic/JDK-8017010.js.EXPECTED +++ b/test/script/basic/JDK-8010710.js.EXPECTED diff --git a/test/script/basic/JDK-8010924.js b/test/script/basic/JDK-8010924.js new file mode 100644 index 00000000..aaa7d129 --- /dev/null +++ b/test/script/basic/JDK-8010924.js @@ -0,0 +1,67 @@ +/* + * 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-8010924: Dealing with undefined property gets you a fatal stack + * + * @test + * @run + * @option -scripting + */ + +load("nashorn:mozilla_compat.js"); + +if (this.non_existent_foo !== undefined) { + fail("this.non_existent_foo is defined!"); +} + +try { + non_existent_foo; + fail("should have thrown ReferenceError"); +} catch (e) { + if (! (e instanceof ReferenceError)) { + fail("ReferenceError expected, got " + e); + } +} + +// try the same via script engine + +var ScriptEngineManager = Java.type("javax.script.ScriptEngineManager"); +var engine = new ScriptEngineManager().getEngineByName("nashorn"); + +engine.eval("load('nashorn:mozilla_compat.js')"); + +if (! engine.eval("this.non_existent_foo === undefined")) { + fail("this.non_existent_foo is not undefined"); +} + +engine.eval(<<EOF + try { + non_existent_foo; + throw new Error("should have thrown ReferenceError"); + } catch (e) { + if (! (e instanceof ReferenceError)) { + throw new Error("ReferenceError expected, got " + e); + } + } +EOF); diff --git a/test/script/basic/JDK-8011209.js b/test/script/basic/JDK-8011209.js new file mode 100644 index 00000000..9098dfaa --- /dev/null +++ b/test/script/basic/JDK-8011209.js @@ -0,0 +1,76 @@ +/* + * 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-8011209: Object.getOwnPropertyDescriptor(function(){"use strict"},"caller").get.length is not 0 + * + * @test + * @run + */ + +var callerPropDesc = Object.getOwnPropertyDescriptor(function(){"use strict"},"caller"); + +var getterLen = callerPropDesc.get.length; +if (getterLen != 0) { + fail("caller's get.length != 0"); +} + +var setterLen = callerPropDesc.set.length; +if (setterLen != 0) { + fail("caller's set.length != 1"); +} + +var argumentsPropDesc = Object.getOwnPropertyDescriptor(function(){"use strict"},"arguments"); + +getterLen = argumentsPropDesc.get.length; +if (getterLen != 0) { + fail("arguments's get.length != 0"); +} + +setterLen = argumentsPropDesc.set.length; +if (setterLen != 0) { + fail("arguments's set.length != 1"); +} + +var strictArgs = (function() { 'use strict'; return arguments; })(); +callerPropDesc = Object.getOwnPropertyDescriptor(strictArgs,"caller"); +getterLen = callerPropDesc.get.length; +if (getterLen != 0) { + fail("argument.caller's get.length != 0"); +} + +setterLen = callerPropDesc.set.length; +if (setterLen != 0) { + fail("argument.caller's set.length != 1"); +} + +calleePropDesc = Object.getOwnPropertyDescriptor(strictArgs,"callee"); +getterLen = calleePropDesc.get.length; +if (getterLen != 0) { + fail("argument.callee's get.length != 0"); +} + +setterLen = calleePropDesc.set.length; +if (setterLen != 0) { + fail("argument.callee's set.length != 1"); +} diff --git a/test/script/basic/JDK-8011237.js b/test/script/basic/JDK-8011237.js new file mode 100644 index 00000000..e9e521a4 --- /dev/null +++ b/test/script/basic/JDK-8011237.js @@ -0,0 +1,39 @@ +/* + * 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-8011237: Object.isExtensible(Object.getOwnPropertyDescriptor(function(){"use strict"},"caller").get) should be false + * + * @test + * @run + */ + +// ECMA Section 13.2.3 The [[ThrowTypeError]] Function Object +// 11. Set the [[Extensible]] internal property of F to false + +var strictFunc = (function() { 'use strict' }); +var strictFuncCallerDesc = Object.getOwnPropertyDescriptor(strictFunc, "caller") +var isExtensible = Object.isExtensible(strictFuncCallerDesc.get); +if (isExtensible) { + fail("strict function caller's getter is extensible!"); +} diff --git a/test/script/basic/JDK-8011274.js b/test/script/basic/JDK-8011274.js new file mode 100644 index 00000000..b483c3e0 --- /dev/null +++ b/test/script/basic/JDK-8011274.js @@ -0,0 +1,48 @@ +/* + * 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-8011324: Object.getOwnPropertyDescriptor(function(){"use strict"},"caller").get.hasOwnProperty("prototype") should be false + * + * @test + * @run + */ + +var strictFunc = (function() { 'use strict' }); +var desc = Object.getOwnPropertyDescriptor(strictFunc, "caller"); +if (desc.get.hasOwnProperty("prototype")) { + fail("strict function's caller getter has 'prototype' property"); +} + +// try few built-ins +if (parseInt.hasOwnProperty("prototype")) { + fail("parseInt.hasOwnProperty('prototype') is true"); +} + +if (parseFloat.hasOwnProperty("prototype")) { + fail("parseFloat.hasOwnProperty('prototype') is true"); +} + +if (isFinite.hasOwnProperty("prototype")) { + fail("isFinite.hasOwnProperty('prototype') is true"); +} diff --git a/test/script/basic/JDK-8011357.js b/test/script/basic/JDK-8011357.js new file mode 100644 index 00000000..40efec5d --- /dev/null +++ b/test/script/basic/JDK-8011357.js @@ -0,0 +1,68 @@ +/* + * 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-8011357: Array.prototype.slice and Array.prototype.splice should not call user defined valueOf of start, end arguments more than once + * + * @test + * @run + */ + +var startValueOf = 0; +var endValueOf = 0; + +[].slice( + { + valueOf: function() { + startValueOf++; + } + }, + { + valueOf: function() { + endValueOf++; + } + } +); + +if (startValueOf !== 1) { + fail("Array.prototype.slice should call valueOf on start arg once"); +} + +if (endValueOf !== 1) { + fail("Array.prototype.slice should call valueOf on end arg once"); +} + +startValueOf = 0; + +[].splice( + { + valueOf: function() { + startValueOf++; + } + } +); + +if (startValueOf !== 1) { + fail("Array.prototype.splice should call valueOf on start arg once"); +} + diff --git a/test/script/basic/JDK-8011362.js b/test/script/basic/JDK-8011362.js new file mode 100644 index 00000000..bda4851a --- /dev/null +++ b/test/script/basic/JDK-8011362.js @@ -0,0 +1,34 @@ +/* + * 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-8011362: Overloaded method resolution foiled by nulls + * + * @test + * @run + */ + +var subject = new (Java.type("jdk.nashorn.test.models.Jdk8011362TestSubject")) + +print(subject.overloaded("", null)) +print(subject.overloaded(0, null)) diff --git a/test/script/basic/JDK-8011362.js.EXPECTED b/test/script/basic/JDK-8011362.js.EXPECTED new file mode 100644 index 00000000..e0eb4dc9 --- /dev/null +++ b/test/script/basic/JDK-8011362.js.EXPECTED @@ -0,0 +1,2 @@ +overloaded(String, String) +overloaded(Double, Double) diff --git a/test/script/basic/JDK-8011365.js b/test/script/basic/JDK-8011365.js new file mode 100644 index 00000000..30802576 --- /dev/null +++ b/test/script/basic/JDK-8011365.js @@ -0,0 +1,72 @@ +/* + * 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-8011365: Array.prototype.join and Array.prototype.toString do not throw TypeError on null, undefined + * + * @test + * @run + */ + +try { + Array.prototype.join.call(null, { toString:function() { throw 2 } }); + fail("should have thrown TypeError"); +} catch (e) { + if (! (e instanceof TypeError)) { + fail("TypeError expected, got " + e); + } +} + +// check all Array.prototype functions to be sure +var names = Object.getOwnPropertyNames(Array.prototype); + +for (var n in names) { + var funcName = names[n]; + // ignore constructor + if (funcName == "constructor") { + continue; + } + + var prop = Array.prototype[funcName]; + if (prop instanceof Function) { + // try 'null' this + try { + prop.call(null); + fail(funcName + " does not throw TypeError on 'null' this"); + } catch (e) { + if (! (e instanceof TypeError)) { + fail("TypeError expected from " + funcName + ", got " + e); + } + } + + // try 'undefined' this + try { + prop.call(undefined); + fail(funcName + " does not throw TypeError on 'undefined' this"); + } catch (e) { + if (! (e instanceof TypeError)) { + fail("TypeError expected from " + funcName + ", got " + e); + } + } + } +} diff --git a/test/script/basic/JDK-8011382.js b/test/script/basic/JDK-8011382.js new file mode 100644 index 00000000..13b3a771 --- /dev/null +++ b/test/script/basic/JDK-8011382.js @@ -0,0 +1,115 @@ +/* + * 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-8011382: Data prototype methods and constructor do not call user defined toISOString, valueOf methods per spec. + * + * @test + * @run + */ + +var yearValueOf = 0; +var monthValueOf = 0; +var dayValueOf = 0; + +var d = new Date( + { + valueOf: function() { yearValueOf++; return NaN; } + }, + { + valueOf: function() { monthValueOf++; return NaN; } + }, + { + valueOf: function() { dayValueOf++; return NaN; } + } +); + +if (yearValueOf !== 1) { + fail("Date constructor does not call valueOf on year argument once"); +} + +if (monthValueOf !== 1) { + fail("Date constructor does not call valueOf on month argument once"); +} + +if (dayValueOf !== 1) { + fail("Date constructor does not call valueOf on day argument once"); +} + +yearValueOf = 0; +monthValueOf = 0; +dayValueOf = 0; + +d = new Date(); + +d.setFullYear( + { + valueOf: function() { yearValueOf++; return NaN; } + }, + { + valueOf: function() { monthValueOf++; return NaN; } + }, + { + valueOf: function() { dayValueOf++; return NaN; } + } +); + +if (yearValueOf !== 1) { + fail("Date setFullYear does not call valueOf on year argument once"); +} + +if (monthValueOf !== 1) { + fail("Date setFullYear does not call valueOf on month argument once"); +} + +if (dayValueOf !== 1) { + fail("Date setFullYear does not call valueOf on day argument once"); +} + +// check toJSON calls toISOString override +var toISOStringCalled = 0; +d = new Date(); +d.toISOString = function() { + toISOStringCalled++; +}; + +d.toJSON(); +if (toISOStringCalled !== 1) { + fail("toISOString was not called by Date.prototype.toJSON once"); +} + +toISOStringCalled = 0; + +// toJSON is generic - try for non-Date object +Date.prototype.toJSON.call({ + toISOString: function() { + toISOStringCalled++; + }, + valueOf: function() { + return 12; + } +}); + +if (toISOStringCalled !== 1) { + fail("toISOString was not called by Date.prototype.toJSON once"); +} diff --git a/test/script/basic/JDK-8011394.js b/test/script/basic/JDK-8011394.js new file mode 100644 index 00000000..070f5d3c --- /dev/null +++ b/test/script/basic/JDK-8011394.js @@ -0,0 +1,45 @@ +/* + * 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-8011394: RegExp.prototype.test() does not call valueOf on lastIndex property as per the spec. + * + * @test + * @run + */ + +var re = new RegExp(); +var lastIndexValueOfCalled = false; + +re.lastIndex = { + valueOf: function() { + lastIndexValueOfCalled = true; + return 0; + } +}; + +re.test(""); + +if (! lastIndexValueOfCalled) { + fail("RegExp.prototype.test() does not call 'valueOf' on 'lastIndex'"); +} diff --git a/test/script/basic/JDK-8011421.js b/test/script/basic/JDK-8011421.js new file mode 100644 index 00000000..c1acef4b --- /dev/null +++ b/test/script/basic/JDK-8011421.js @@ -0,0 +1,51 @@ +/* + * 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-8011421: When using Object.defineProperty on arrays, PropertyDescriptor's property accessors are invoked multiple times + * + * @test + * @run + */ + +var configurableGetterCalled = 0; + +// create a property descriptor object with "configurable" +// property with a user defined getter +var propDesc = Object.defineProperty({}, + "configurable", + { + get: function() { + configurableGetterCalled++; + return false + } + } +); + +// make array length non-configurable +Object.defineProperty([], "length", propDesc); + +// above should have called "configurable" getter only once +if (configurableGetterCalled !== 1) { + fail("defineProperty on array should call propDesc getters only once"); +} diff --git a/test/script/basic/JDK-8011543.js b/test/script/basic/JDK-8011543.js new file mode 100644 index 00000000..49baef71 --- /dev/null +++ b/test/script/basic/JDK-8011543.js @@ -0,0 +1,42 @@ +/* + * 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-8011543: "".split(undefined,{valueOf:function(){throw 2}}) does not throw exception + * + * @test + * @run + */ + +try { + "".split(undefined,{ + valueOf: function() { + throw 42; + } + }); + fail("should have thrown 42"); +} catch (e) { + if (e != 42) { + fail("expected 42 to be thrown"); + } +} diff --git a/test/script/basic/JDK-8011552.js b/test/script/basic/JDK-8011552.js new file mode 100644 index 00000000..f0735a00 --- /dev/null +++ b/test/script/basic/JDK-8011552.js @@ -0,0 +1,37 @@ +/* + * 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-8011552: Arrays with missing elements are not properly sorted + * + * @test + * @run + */ + +if ([,void 0].sort().hasOwnProperty("1")) { + fail("missing element found in sorted array"); +} + +if ([1,,2,,-1].sort().toString() != "-1,1,2,,") { + faiil("missing elements are not at the end of sorted array"); +} diff --git a/test/script/basic/JDK-8011555.js b/test/script/basic/JDK-8011555.js new file mode 100644 index 00000000..c65ad612 --- /dev/null +++ b/test/script/basic/JDK-8011555.js @@ -0,0 +1,42 @@ +/* + * 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-8011555: Invalid class name in with block with JavaImporter causes MH type mismatch + * + * @test + * @run + */ + +with(new JavaImporter()) { + try { + new X() + print("Expected to fail!") + } catch(e) { + // We expect to get a TypeError for trying to use __noSuchMethod__ as + // a constructor. Before we fixed this bug, we were getting a runtime + // exception with MH type mismatch on a MH.foldArguments within the + // WithObject code instead. + print(e) + } +} diff --git a/test/script/basic/JDK-8011555.js.EXPECTED b/test/script/basic/JDK-8011555.js.EXPECTED new file mode 100644 index 00000000..c296217a --- /dev/null +++ b/test/script/basic/JDK-8011555.js.EXPECTED @@ -0,0 +1 @@ +TypeError: function __noSuchMethod__() { [native code] } is not a constructor function diff --git a/test/script/basic/JDK-8011578.js b/test/script/basic/JDK-8011578.js new file mode 100644 index 00000000..9a2f7438 --- /dev/null +++ b/test/script/basic/JDK-8011578.js @@ -0,0 +1,42 @@ +/* + * 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-8011578 : -Dnashorn.unstable.relink.threshold=1 causes tests to fail. + * + * @test + * @option -Dnashorn.unstable.relink.threshold=1 + * @run + */ + +load(__DIR__ + "NASHORN-296.js"); +load(__DIR__ + "NASHORN-691.js"); +load(__DIR__ + "calllink.js"); +load(__DIR__ + "nosuchproperty.js"); + +__noSuchProperty__ = function(x) { + print(x); + return x; +} + +print(this["find"]); diff --git a/test/script/basic/JDK-8011578.js.EXPECTED b/test/script/basic/JDK-8011578.js.EXPECTED new file mode 100644 index 00000000..aa7184a7 --- /dev/null +++ b/test/script/basic/JDK-8011578.js.EXPECTED @@ -0,0 +1,22 @@ +o.foo = 33 +o.foo = 44 +o.foo = 3 +o.foo = hello +obj1.func called +obj2.func called +no such method: func +obj4's prototype func called +MyConstructor.prototype.func +MyConstructor.prototype.func +obj1.func called +obj2.func called +new obj3.func called +new obj4.func called +all new MyConstructor.prototype.func +all new MyConstructor.prototype.func +obj.__noSuchProperty__ for foo +new obj.__noSuchProperty__ for foo +proto.__noSuchProperty__ for foo +new proto.__noSuchProperty__ for foo +find +find diff --git a/test/script/basic/JDK-8011714.js b/test/script/basic/JDK-8011714.js new file mode 100644 index 00000000..dac00743 --- /dev/null +++ b/test/script/basic/JDK-8011714.js @@ -0,0 +1,68 @@ +/* + * 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-8011714: Regexp decimal escape handling still not correct + * + * @test + * @run + */ + +// \0 should be interpreted as <NUL> character here +print(/\08/.test("\x008")); +print(/[\08]/.test("8")); +print(/[\08]/.test("\x00")); + +// Can't be converted to octal thus encoded as literal char sequence +print(/\8/.exec("\\8")); +print(/[\8]/.exec("\\")); +print(/[\8]/.exec("8")); + +// 0471 is too high for an octal escape so it is \047 outside a character class +// and \\471 inside a character class +print(/\471/.exec("\x271")); +print(/[\471]/.exec("1")); +print(/[\471]/.exec("\x27")); + +// 0366 is a valid octal escape (246) +print(/\366/.test("\xf6")); +print(/[\366]/.test("\xf6")); +print(/[\366]/.test("\xf6")); + +// more tests for conversion of invalid backreferences to octal escapes or literals +print(/(a)(b)(c)(d)\4/.exec("abcdd")); +print(/(a)(b)(c)(d)\4x/.exec("abcddx")); +print(/(a)(b)(c)(d)\47/.exec("abcdd7")); +print(/(a)(b)(c)(d)\47/.exec("abcd\x27")); +print(/(a)(b)(c)(d)\47xyz/.exec("abcd\x27xyz")); +print(/(a)(b)(c)(d)[\47]/.exec("abcd\x27")); +print(/(a)(b)(c)(d)[\47]xyz/.exec("abcd\x27xyz")); +print(/(a)(b)(c)(d)\48/.exec("abcd\x048")); +print(/(a)(b)(c)(d)\48xyz/.exec("abcd\x048xyz")); +print(/(a)(b)(c)(d)[\48]/.exec("abcd\x04")); +print(/(a)(b)(c)(d)[\48]xyz/.exec("abcd\x04xyz")); +print(/(a)(b)(c)(d)\84/.exec("abcd84")); +print(/(a)(b)(c)(d)\84xyz/.exec("abcd84xyz")); +print(/(a)(b)(c)(d)[\84]/.exec("abcd8")); +print(/(a)(b)(c)(d)[\84]xyz/.exec("abcd8xyz")); + diff --git a/test/script/basic/JDK-8011714.js.EXPECTED b/test/script/basic/JDK-8011714.js.EXPECTED new file mode 100644 index 00000000..c5a8b345 --- /dev/null +++ b/test/script/basic/JDK-8011714.js.EXPECTED @@ -0,0 +1,27 @@ +true +true +true +8 +null +8 +'1 +1 +' +true +true +true +abcdd,a,b,c,d +abcddx,a,b,c,d +null +abcd',a,b,c,d +abcd'xyz,a,b,c,d +abcd',a,b,c,d +abcd'xyz,a,b,c,d +abcd8,a,b,c,d +abcd8xyz,a,b,c,d +abcd,a,b,c,d +abcdxyz,a,b,c,d +abcd84,a,b,c,d +abcd84xyz,a,b,c,d +abcd8,a,b,c,d +abcd8xyz,a,b,c,d diff --git a/test/script/basic/JDK-8011749.js b/test/script/basic/JDK-8011749.js new file mode 100644 index 00000000..41d9ac64 --- /dev/null +++ b/test/script/basic/JDK-8011749.js @@ -0,0 +1,38 @@ +/* + * 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-8011749: Bugs with empty character class handling + * + * @test + * @run + */ + +// empty class in alternative +print(/[]|[^]/.exec("a")); +print(/[]|[]/.test("a")); +print(/[]|[]|[a]/.exec("a")); + +// empty class in negative lookahead +print(/(?![])/.test("")); +print(/(?![])./.exec("a")); diff --git a/test/script/basic/JDK-8011749.js.EXPECTED b/test/script/basic/JDK-8011749.js.EXPECTED new file mode 100644 index 00000000..82d8c103 --- /dev/null +++ b/test/script/basic/JDK-8011749.js.EXPECTED @@ -0,0 +1,5 @@ +a +false +a +true +a diff --git a/test/script/basic/JDK-8011756.js b/test/script/basic/JDK-8011756.js new file mode 100644 index 00000000..c0e8d834 --- /dev/null +++ b/test/script/basic/JDK-8011756.js @@ -0,0 +1,59 @@ +/* + * 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-8011756: Wrong characters supported in RegExp \c escape + * + * @test + * @run + */ + + +// Invalid control letters should be escaped: +print(/\cı/.test("\x09")); +print(/\cı/.test("\\cı")); + +print(/\cſ/.test("\x13")); +print(/\cſ/.test("\\cſ")); + +print(/[\cſ]/.test("\x13")); +print(/[\cſ]/.test("\\")); +print(/[\cſ]/.test("c")); +print(/[\cſ]/.test("ſ")); + +print(/[\c#]/.test("\\")); +print(/[\c#]/.test("c")); +print(/[\c#]/.test("#")); + +// The characters that are supported by other engines are '0'-'9', '_': +print(/[\c0]/.test("\x10")); +print(/[\c1]/.test("\x11")); +print(/[\c2]/.test("\x12")); +print(/[\c3]/.test("\x13")); +print(/[\c4]/.test("\x14")); +print(/[\c5]/.test("\x15")); +print(/[\c6]/.test("\x16")); +print(/[\c7]/.test("\x17")); +print(/[\c8]/.test("\x18")); +print(/[\c9]/.test("\x19")); +print(/[\c_]/.test("\x1F")); diff --git a/test/script/basic/JDK-8011756.js.EXPECTED b/test/script/basic/JDK-8011756.js.EXPECTED new file mode 100644 index 00000000..63722a57 --- /dev/null +++ b/test/script/basic/JDK-8011756.js.EXPECTED @@ -0,0 +1,22 @@ +false +true +false +true +false +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true diff --git a/test/script/basic/JDK-8011960.js b/test/script/basic/JDK-8011960.js new file mode 100644 index 00000000..f691d2c8 --- /dev/null +++ b/test/script/basic/JDK-8011960.js @@ -0,0 +1,53 @@ +/* + * 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-8011960: [2,1].sort(null) should throw TypeError + * + * @test + * @run + */ + +function check(func) { + try { + [2,1].sort(func); + fail("should have thrown TypeError for :" + func); + } catch (e) { + if (! (e instanceof TypeError)) { + fail("TypeError not thrown for sort comparefn: " + func); + } + } +} + +// should not result in TypeError for undefined +[1, 2].sort(undefined); + +// TypeError for null +check(null); + +// should result in TypeError other non-callable params +check(32); +check("foo"); +check(false); +check({}); +check([]); diff --git a/test/script/basic/JDK-8011974.js b/test/script/basic/JDK-8011974.js new file mode 100644 index 00000000..f3f9b4ae --- /dev/null +++ b/test/script/basic/JDK-8011974.js @@ -0,0 +1,39 @@ +/* + * 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-8011974: Comparator function returning negative and positive Infinity does not work as expected with Array.prototype.sort + * + * @test + * @run + */ + +function compare(x, y) { + return x < y? -Infinity : (x > y? Infinity: 0) +} + +var sorted = [5, 4, 3, 2, 1].sort(compare); + +if (sorted + '' != "1,2,3,4,5") { + fail("Array.prototype.sort does not work when compare returns +/-Infinity"); +} diff --git a/test/script/basic/JDK-8011980.js b/test/script/basic/JDK-8011980.js new file mode 100644 index 00000000..a83f2864 --- /dev/null +++ b/test/script/basic/JDK-8011980.js @@ -0,0 +1,34 @@ +/* + * 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-8011980: Allow NUL character in character class + * + * @test + * @run + */ + +print(RegExp("\0").test("\0")); +print(RegExp("[\0]").test("\0")); +print(RegExp("[\x00]").test("\0")); +print(RegExp("[\u0000]").test("\0")); diff --git a/test/script/basic/JDK-8011980.js.EXPECTED b/test/script/basic/JDK-8011980.js.EXPECTED new file mode 100644 index 00000000..1140ff52 --- /dev/null +++ b/test/script/basic/JDK-8011980.js.EXPECTED @@ -0,0 +1,4 @@ +true +true +true +true diff --git a/test/script/basic/JDK-8012240.js b/test/script/basic/JDK-8012240.js new file mode 100644 index 00000000..2ac6eaf7 --- /dev/null +++ b/test/script/basic/JDK-8012240.js @@ -0,0 +1,47 @@ +/* + * 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-8012240: Array.prototype.map.call({length: -1, get 0(){throw 0}}, function(){}).length does not throw error + * + * @test + * @run + */ + +var in_getter_for_0 = false; + +try { + Array.prototype.map.call( + { + length: -1, + get 0() { + in_getter_for_0 = true; + throw 0; + } + }, + function(){}).length; +} catch (e) { + if (e !== 0 || !in_getter_for_0) { + fail("should have thrown error from getter for '0'th element"); + } +} diff --git a/test/script/basic/JDK-8012334.js b/test/script/basic/JDK-8012334.js new file mode 100644 index 00000000..4a1c7a3c --- /dev/null +++ b/test/script/basic/JDK-8012334.js @@ -0,0 +1,80 @@ +/* + * 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-8012334: ToUint32, ToInt32, and ToUint16 don't conform to spec + * + * @test + * @run + */ + + +function test(val) { + print(val | 0); + print(val >> 0); + print(val >>> 0); + print(1 >>> val); + print(parseInt("10", val)); +} + +test(0); +test(-0); +test('Infinity'); +test('+Infinity'); +test('-Infinity'); +test(Number.POSITIVE_INFINITY); +test(Number.NEGATIVE_INFINITY); +test(Number.NaN); +test(Number.MIN_VALUE); +test(-Number.MIN_VALUE); +test(1); +test(-1); +test(0.1); +test(-0.1); +test(1.1); +test(-1.1); +test(9223372036854775807); +test(-9223372036854775808); +test('9223372036854775807'); +test('-9223372036854775808'); +test(2147483647); +test(2147483648); +test(2147483649); +test(-2147483647); +test(-2147483648); +test(-2147483649); +test(4294967295); +test(4294967296); +test(4294967297); +test(-4294967295); +test(-4294967296); +test(-4294967297); +test(1e23); +test(-1e23); +test(1e24); +test(-1e24); +test(1e25); +test(-1e25); +test(1e26); +test(-1e26); + diff --git a/test/script/basic/JDK-8012334.js.EXPECTED b/test/script/basic/JDK-8012334.js.EXPECTED new file mode 100644 index 00000000..d9b16f41 --- /dev/null +++ b/test/script/basic/JDK-8012334.js.EXPECTED @@ -0,0 +1,200 @@ +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +1 +1 +1 +0 +NaN +-1 +-1 +4294967295 +0 +NaN +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +1 +1 +1 +0 +NaN +-1 +-1 +4294967295 +0 +NaN +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 +2147483647 +2147483647 +2147483647 +0 +NaN +-2147483648 +-2147483648 +2147483648 +1 +NaN +-2147483647 +-2147483647 +2147483649 +0 +NaN +-2147483647 +-2147483647 +2147483649 +0 +NaN +-2147483648 +-2147483648 +2147483648 +1 +NaN +2147483647 +2147483647 +2147483647 +0 +NaN +-1 +-1 +4294967295 +0 +NaN +0 +0 +0 +1 +10 +1 +1 +1 +0 +NaN +1 +1 +1 +0 +NaN +0 +0 +0 +1 +10 +-1 +-1 +4294967295 +0 +NaN +-167772160 +-167772160 +4127195136 +1 +NaN +167772160 +167772160 +167772160 +1 +NaN +-1610612736 +-1610612736 +2684354560 +1 +NaN +1610612736 +1610612736 +1610612736 +1 +NaN +-2147483648 +-2147483648 +2147483648 +1 +NaN +-2147483648 +-2147483648 +2147483648 +1 +NaN +0 +0 +0 +1 +10 +0 +0 +0 +1 +10 diff --git a/test/script/basic/JDK-8012457.js b/test/script/basic/JDK-8012457.js new file mode 100644 index 00000000..2f71a9a9 --- /dev/null +++ b/test/script/basic/JDK-8012457.js @@ -0,0 +1,46 @@ +/* + * 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-8012457: Function.prototype.apply should accept any array-like argument for function arguments + * + * @test + * @run + */ + +// no exception for these +Function().apply(null, {length: null}) +Function().apply(null, {length: 0.1}) + +// getter should be called +var getter_0_called = false; + +Function().apply(null, + Object.defineProperty([],"0", + { get: function(){ getter_0_called = true; return 0 } + }) +); + +if (! getter_0_called) { + fail("getter for '0' of arguments array not called"); +} diff --git a/test/script/basic/JDK-8012460.js b/test/script/basic/JDK-8012460.js new file mode 100644 index 00000000..0b41298d --- /dev/null +++ b/test/script/basic/JDK-8012460.js @@ -0,0 +1,59 @@ +/* + * 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-8012460: RegExp regression + * + * @test + * @run + */ + + +var semver = "\\s*[v=]*\\s*([0-9]+)" // major + + "\\.([0-9]+)" // minor + + "\\.([0-9]+)" // patch + + "(-[0-9]+-?)?" // build + + "([a-zA-Z-+][a-zA-Z0-9-\.:]*)?" // tag + , exprComparator = "^((<|>)?=?)\s*("+semver+")$|^$"; +var validComparator = new RegExp("^"+exprComparator+"$"); + + +print(exprComparator); +print(">=0.6.0-".match(validComparator)); +print("=0.6.0-".match(validComparator)); +print("0.6.0-".match(validComparator)); +print("<=0.6.0-".match(validComparator)); +print(">=0.6.0-a:b-c.d".match(validComparator)); +print("=0.6.0-a:b-c.d".match(validComparator)); +print("0.6.0+a:b-c.d".match(validComparator)); +print("<=0.6.0+a:b-c.d".match(validComparator)); + +print(/[a-zA-Z-+]/.exec("a")); +print(/[a-zA-Z-+]/.exec("b")); +print(/[a-zA-Z-+]/.exec("y")); +print(/[a-zA-Z-+]/.exec("z")); +print(/[a-zA-Z-+]/.exec("B")); +print(/[a-zA-Z-+]/.exec("Y")); +print(/[a-zA-Z-+]/.exec("Z")); +print(/[a-zA-Z-+]/.exec("-")); +print(/[a-zA-Z-+]/.exec("+")); diff --git a/test/script/basic/JDK-8012460.js.EXPECTED b/test/script/basic/JDK-8012460.js.EXPECTED new file mode 100644 index 00000000..917cac54 --- /dev/null +++ b/test/script/basic/JDK-8012460.js.EXPECTED @@ -0,0 +1,18 @@ +^((<|>)?=?)s*(\s*[v=]*\s*([0-9]+)\.([0-9]+)\.([0-9]+)(-[0-9]+-?)?([a-zA-Z-+][a-zA-Z0-9-.:]*)?)$|^$ +>=0.6.0-,>=,>,0.6.0-,0,6,0,,- +=0.6.0-,=,,0.6.0-,0,6,0,,- +0.6.0-,,,0.6.0-,0,6,0,,- +<=0.6.0-,<=,<,0.6.0-,0,6,0,,- +>=0.6.0-a:b-c.d,>=,>,0.6.0-a:b-c.d,0,6,0,,-a:b-c.d +=0.6.0-a:b-c.d,=,,0.6.0-a:b-c.d,0,6,0,,-a:b-c.d +0.6.0+a:b-c.d,,,0.6.0+a:b-c.d,0,6,0,,+a:b-c.d +<=0.6.0+a:b-c.d,<=,<,0.6.0+a:b-c.d,0,6,0,,+a:b-c.d +a +b +y +z +B +Y +Z +- ++ diff --git a/test/script/basic/JDK-8012462.js b/test/script/basic/JDK-8012462.js new file mode 100644 index 00000000..4e015a99 --- /dev/null +++ b/test/script/basic/JDK-8012462.js @@ -0,0 +1,47 @@ +/* + * 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-8012462: Date.prototype.toJSON does not handle non-Date 'this' as per the spec. + * + * @test + * @run + */ + +var toJSON = Date.prototype.toJSON; + +function checkJSON(value, expected) { + var res = toJSON.call({ + valueOf: function() { return value; }, + toISOString: function() { return value; } + }); + + if (res !== expected) { + fail("Date.prototype.toJSON does not work for non-Date 'this'"); + } +} + +checkJSON(NaN, null); +checkJSON(-Infinity, null); +checkJSON(Infinity, null); +checkJSON("foo", "foo"); diff --git a/test/script/basic/JDK-8012931.js b/test/script/basic/JDK-8012931.js new file mode 100644 index 00000000..c3119b93 --- /dev/null +++ b/test/script/basic/JDK-8012931.js @@ -0,0 +1,38 @@ +/* + * 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-8012931: NativeDate.safeToString() throws RangeError for invalid date + * + * @test + * @run + */ + +var d = new Date(NaN); +d.toString = Number.prototype.toString; + +try { + d.toString(); +} catch(e) { + print(e); +} diff --git a/test/script/basic/JDK-8012931.js.EXPECTED b/test/script/basic/JDK-8012931.js.EXPECTED new file mode 100644 index 00000000..0e84e7da --- /dev/null +++ b/test/script/basic/JDK-8012931.js.EXPECTED @@ -0,0 +1 @@ +TypeError: [Date Invalid Date] is not a Number diff --git a/test/script/basic/JDK-8013131.js b/test/script/basic/JDK-8013131.js new file mode 100644 index 00000000..dfff8ac6 --- /dev/null +++ b/test/script/basic/JDK-8013131.js @@ -0,0 +1,66 @@ +/* + * 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-8013131: Various compatibility issues in String.prototype.split() + * + * @test + * @run + */ + + +// Make sure limit is honored with undefined/empty separator +print(JSON.stringify("aa".split(undefined, 0))); +print(JSON.stringify("abc".split("", 1))); + +// Make sure limit is honored with capture groups +print(JSON.stringify("aa".split(/(a)/, 1))); +print(JSON.stringify("aa".split(/(a)/, 2))); +print(JSON.stringify("aa".split(/((a))/, 1))); +print(JSON.stringify("aa".split(/((a))/, 2))); + +// Empty capture group at end of string should be ignored +print(JSON.stringify("aaa".split(/((?:))/))); + +// Tests below are to make sure that split does not read or write lastIndex property +var r = /a/; +r.lastIndex = { + valueOf: function(){throw 2} +}; +print(JSON.stringify("aa".split(r))); + +r = /a/g; +r.lastIndex = 100; +print(JSON.stringify("aa".split(r))); +print(r.lastIndex); + +r = /((?:))/g; +r.lastIndex = 100; +print(JSON.stringify("aaa".split(r))); +print(r.lastIndex); + +// Make sure lastIndex is not updated on non-global regexp +r = /a/; +r.lastIndex = 100; +print(JSON.stringify(r.exec("aaa"))); +print(r.lastIndex); diff --git a/test/script/basic/JDK-8013131.js.EXPECTED b/test/script/basic/JDK-8013131.js.EXPECTED new file mode 100644 index 00000000..9e0fa963 --- /dev/null +++ b/test/script/basic/JDK-8013131.js.EXPECTED @@ -0,0 +1,14 @@ +[] +["a"] +[""] +["","a"] +[""] +["","a"] +["a","","a","","a"] +["","",""] +["","",""] +100 +["a","","a","","a"] +100 +["a"] +100 diff --git a/test/script/basic/JDK-8013167.js b/test/script/basic/JDK-8013167.js new file mode 100644 index 00000000..ec3c71a1 --- /dev/null +++ b/test/script/basic/JDK-8013167.js @@ -0,0 +1,32 @@ +/* + * 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-8013167: Vararg constructor was not found + * + * @test + * @run + */ + +var x = new Packages.jdk.nashorn.test.models.VarArgConstructor(1, false, "a", "b", "c") +print(x.indicator)
\ No newline at end of file diff --git a/test/script/basic/JDK-8013167.js.EXPECTED b/test/script/basic/JDK-8013167.js.EXPECTED new file mode 100644 index 00000000..e632d3be --- /dev/null +++ b/test/script/basic/JDK-8013167.js.EXPECTED @@ -0,0 +1 @@ +vararg diff --git a/test/script/basic/JDK-8013325.js b/test/script/basic/JDK-8013325.js new file mode 100644 index 00000000..e1faa7b7 --- /dev/null +++ b/test/script/basic/JDK-8013325.js @@ -0,0 +1,44 @@ +/* + * 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-8013325: function named 'arguments' should still access arguments object within itself. + * Its parent should however see the function and not an arguments object. + * + * @test + * @run + */ + +function x() { + // x doesn't see an arguments object as it has a nested function with that name + // so it'll invoke the function. + arguments("a", "b", "c"); + + function arguments(x, y, z) { + // The function 'arguments' OTOH can't see itself; if it uses the + // identifier 'arguments', it'll see its own arguments object. + print(arguments) + print(x + " " + y + " " + z) + } +} +x() diff --git a/test/script/basic/JDK-8013325.js.EXPECTED b/test/script/basic/JDK-8013325.js.EXPECTED new file mode 100644 index 00000000..e2dc7e54 --- /dev/null +++ b/test/script/basic/JDK-8013325.js.EXPECTED @@ -0,0 +1,2 @@ +[object Arguments] +a b c diff --git a/test/script/basic/JDK-8013337.js b/test/script/basic/JDK-8013337.js new file mode 100644 index 00000000..b95da8bd --- /dev/null +++ b/test/script/basic/JDK-8013337.js @@ -0,0 +1,71 @@ +/* + * 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-8013337: Issues with Date.prototype's get, set functions + * + * @test + * @option -timezone=Asia/Calcutta + * @run + */ + +function check(str) { + print(str + " = " + eval(str)); +} + +check('new Date(NaN).setFullYear(NaN)'); +check('new Date(0).setYear(70)'); +check('new Date(0).setYear(NaN)'); +check('new Date(NaN).setYear(70)'); +check('new Date(NaN).getTimezoneOffset()'); + +function checkGetterCalled(func) { + var getterCalled = false; + + new Date(NaN)[func]( { valueOf: function() { getterCalled = true } } ); + + if (getterCalled) { + print("Date.prototype." + func + " calls valueOf on arg"); + } +} + +checkGetterCalled("setMilliseconds"); +checkGetterCalled("setUTCMilliseconds"); +checkGetterCalled("setSeconds"); +checkGetterCalled("setUTCSeconds"); +checkGetterCalled("setMinutes"); +checkGetterCalled("setUTCMinutes"); +checkGetterCalled("setHours"); +checkGetterCalled("setUTCHours"); +checkGetterCalled("setDate"); +checkGetterCalled("setUTCDate"); +checkGetterCalled("setMonth"); +checkGetterCalled("setUTCMonth"); + +try { + Date.prototype.setTime.call({}, { valueOf: function() { throw "err" } }) +} catch (e) { + if (! (e instanceof TypeError)) { + fail("TypeError expected, got " + e); + } +} diff --git a/test/script/basic/JDK-8013337.js.EXPECTED b/test/script/basic/JDK-8013337.js.EXPECTED new file mode 100644 index 00000000..fbd2d655 --- /dev/null +++ b/test/script/basic/JDK-8013337.js.EXPECTED @@ -0,0 +1,17 @@ +new Date(NaN).setFullYear(NaN) = NaN +new Date(0).setYear(70) = 0 +new Date(0).setYear(NaN) = NaN +new Date(NaN).setYear(70) = -19800000 +new Date(NaN).getTimezoneOffset() = NaN +Date.prototype.setMilliseconds calls valueOf on arg +Date.prototype.setUTCMilliseconds calls valueOf on arg +Date.prototype.setSeconds calls valueOf on arg +Date.prototype.setUTCSeconds calls valueOf on arg +Date.prototype.setMinutes calls valueOf on arg +Date.prototype.setUTCMinutes calls valueOf on arg +Date.prototype.setHours calls valueOf on arg +Date.prototype.setUTCHours calls valueOf on arg +Date.prototype.setDate calls valueOf on arg +Date.prototype.setUTCDate calls valueOf on arg +Date.prototype.setMonth calls valueOf on arg +Date.prototype.setUTCMonth calls valueOf on arg diff --git a/test/script/basic/JDK-8013444.js b/test/script/basic/JDK-8013444.js new file mode 100644 index 00000000..166575cd --- /dev/null +++ b/test/script/basic/JDK-8013444.js @@ -0,0 +1,49 @@ +/* + * 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-8013444: JSON.parse does not invoke "reviver" callback as per spec. + * + * @test + * @run + */ + + +var type = typeof JSON.parse('{}',function(){}) +print("type is " + type); + +var obj = JSON.parse('{"name": "nashorn"}', + function(k, v) { + if (k === "") return v; + return v.toUpperCase(); + }); +print(JSON.stringify(obj)) + +var array = + JSON.parse("[1, 3, 5, 7, 9, 11]", + function(k, v) { + if (k === "") return v; + return v*2; + } + ); +print(array) diff --git a/test/script/basic/JDK-8013444.js.EXPECTED b/test/script/basic/JDK-8013444.js.EXPECTED new file mode 100644 index 00000000..2214e49b --- /dev/null +++ b/test/script/basic/JDK-8013444.js.EXPECTED @@ -0,0 +1,3 @@ +type is undefined +{"name":"NASHORN"} +2,6,10,14,18,22 diff --git a/test/script/basic/javaclassoverrides.js b/test/script/basic/javaclassoverrides.js new file mode 100644 index 00000000..e7ad61d8 --- /dev/null +++ b/test/script/basic/javaclassoverrides.js @@ -0,0 +1,86 @@ +/* + * 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. + */ + +/** + * Check behavior of class-level overrides. + * + * @test + * @run + */ + + +// Make two classes with class overrides + +var R1 = Java.extend(java.lang.Runnable, { + run: function() { + print("R1.run() invoked") + } +}) + +var R2 = Java.extend(java.lang.Runnable, { + run: function() { + print("R2.run() invoked") + } +}) + +var r1 = new R1 +var r2 = new R2 +// Create one with an instance-override too +var r3 = new R2(function() { print("r3.run() invoked") }) + +// Run 'em - we're passing them through a Thread to make sure they indeed +// are full-blown Runnables +function runInThread(r) { + var t = new java.lang.Thread(r) + t.start() + t.join() +} +runInThread(r1) +runInThread(r2) +runInThread(r3) + +// Two class-override classes differ +print("r1.class != r2.class: " + (r1.class != r2.class)) +// However, adding instance-overrides doesn't change the class +print("r2.class == r3.class: " + (r2.class == r3.class)) + +function checkAbstract(r) { + try { + r.run() + print("Expected to fail!") + } catch(e) { + print("Got exception: " + e) + } +} + +// Check we're hitting UnsupportedOperationException if neither class +// overrides nor instance overrides are present +var RAbstract = Java.extend(java.lang.Runnable, {}) +checkAbstract(new RAbstract()) // class override (empty) +checkAbstract(new RAbstract() {}) // class+instance override (empty) + +// Check we delegate to superclass if neither class +// overrides nor instance overrides are present +var ExtendsList = Java.extend(java.util.ArrayList, {}) +print("(new ExtendsList).size() = " + (new ExtendsList).size()) +print("(new ExtendsList(){}).size() = " + (new ExtendsList(){}).size())
\ No newline at end of file diff --git a/test/script/basic/javaclassoverrides.js.EXPECTED b/test/script/basic/javaclassoverrides.js.EXPECTED new file mode 100644 index 00000000..6c534302 --- /dev/null +++ b/test/script/basic/javaclassoverrides.js.EXPECTED @@ -0,0 +1,9 @@ +R1.run() invoked +R2.run() invoked +r3.run() invoked +r1.class != r2.class: true +r2.class == r3.class: true +Got exception: java.lang.UnsupportedOperationException +Got exception: java.lang.UnsupportedOperationException +(new ExtendsList).size() = 0 +(new ExtendsList(){}).size() = 0 diff --git a/test/script/basic/try2.js b/test/script/basic/try2.js new file mode 100644 index 00000000..8a59c89b --- /dev/null +++ b/test/script/basic/try2.js @@ -0,0 +1,49 @@ +/* + * 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. + */ + +/** + * Try throw test - nest finally + * + * @test + * @run + */ + +function f() { + print("a"); + try { + print("b"); + } finally { + print("c"); + try { + print("d"); + } finally { + print("e"); + } + print("f"); + } + print("g"); +} + +f(); + +print("done"); diff --git a/test/script/basic/try2.js.EXPECTED b/test/script/basic/try2.js.EXPECTED new file mode 100644 index 00000000..603b8cd1 --- /dev/null +++ b/test/script/basic/try2.js.EXPECTED @@ -0,0 +1,8 @@ +a +b +c +d +e +f +g +done diff --git a/test/script/error/JDK-8008814-1.js b/test/script/error/JDK-8008814-1.js new file mode 100644 index 00000000..324d090c --- /dev/null +++ b/test/script/error/JDK-8008814-1.js @@ -0,0 +1,34 @@ +/* + * 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. + */ + +/** + * NASHORN-8008814: it's a compile time error to have a nested function declaration when there's an option to treat it as an error + * + * @option --function-statement-error + * @test/compile-error + */ + +if(true) { + function g() { + } +} diff --git a/test/script/error/JDK-8008814-1.js.EXPECTED b/test/script/error/JDK-8008814-1.js.EXPECTED new file mode 100644 index 00000000..91437255 --- /dev/null +++ b/test/script/error/JDK-8008814-1.js.EXPECTED @@ -0,0 +1,3 @@ +test/script/error/JDK-8008814-1.js:32:2 Function declarations can only occur at program or function body level. You should use a function expression here instead. + function g() { + ^ diff --git a/test/script/error/JDK-8008814-2.js b/test/script/error/JDK-8008814-2.js new file mode 100644 index 00000000..079b253f --- /dev/null +++ b/test/script/error/JDK-8008814-2.js @@ -0,0 +1,34 @@ +/* + * 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. + */ + +/** + * NASHORN-8008814: it's a compile time error to have a nested function declaration in strict mode + * + * @test/compile-error + */ + +"use strict"; +if(true) { + function g() { + } +} diff --git a/test/script/error/JDK-8008814-2.js.EXPECTED b/test/script/error/JDK-8008814-2.js.EXPECTED new file mode 100644 index 00000000..f79c679c --- /dev/null +++ b/test/script/error/JDK-8008814-2.js.EXPECTED @@ -0,0 +1,3 @@ +test/script/error/JDK-8008814-2.js:32:2 In strict mode, function declarations can only occur at program or function body level. You should use a function expression here instead. + function g() { + ^ diff --git a/test/script/trusted/logcoverage.js b/test/script/trusted/logcoverage.js new file mode 100644 index 00000000..a2cd7a0f --- /dev/null +++ b/test/script/trusted/logcoverage.js @@ -0,0 +1,108 @@ +/* + * 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. + */ + +/** + * mh_coverage.js + * Screen scrape various logs to ensure that we cover enough functionality, + * e.g. method handle instrumentation + * + * @test + * @run + */ + +/* + * creates new script engine initialized with given options and + * runs given code on it. Returns standard output captured. + */ + +function runScriptEngine(opts, name) { + var imports = new JavaImporter( + Packages.jdk.nashorn.api.scripting, + java.io, java.lang, java.util); + + with(imports) { + var fac = new NashornScriptEngineFactory(); + // get current System.err + var oldErr = System.err; + var oldOut = System.out; + var baosErr = new ByteArrayOutputStream(); + var newErr = new PrintStream(baosErr); + var baosOut = new ByteArrayOutputStream(); + var newOut = new PrintStream(baosOut); + try { + // set new standard err + System.setErr(newErr); + System.setOut(newOut); + var strType = Java.type("java.lang.String"); + var engine = fac.getScriptEngine(Java.toJavaArray(opts, strType)); + var reader = new java.io.FileReader(name); + engine.eval(reader); + newErr.flush(); + newOut.flush(); + return new java.lang.String(baosErr.toByteArray()); + } finally { + // restore System.err to old value + System.setErr(oldErr); + System.setOut(oldOut); + } + } +} + +var str; + +var methodsCalled = [ + 'asCollector', + 'asType', + 'bindTo', + 'dropArguments', + 'explicitCastArguments', + 'filterArguments', + 'filterReturnValue', + 'findStatic', + 'findVirtual', + 'foldArguments', + 'getter', + 'guardWithTest', + 'insertArguments', + 'methodType', + 'setter' +]; + +function check(str, strs) { + for each (s in strs) { + if (s.indexOf(str) !== -1) { + continue; + } + print(method + "not found"); + return; + } + print("check ok!"); +} + +str = runScriptEngine([ "--log=codegen,compiler=finest,methodhandles=finest,fields=finest" ], __DIR__ + "../basic/NASHORN-19.js"); + +check(str, methodsCalled); +check(str, ['return', 'get', 'set', '[fields]']); +check(str, ['codegen']); + +print("hello, world!"); diff --git a/test/script/trusted/logcoverage.js.EXPECTED b/test/script/trusted/logcoverage.js.EXPECTED new file mode 100644 index 00000000..c3e64ce5 --- /dev/null +++ b/test/script/trusted/logcoverage.js.EXPECTED @@ -0,0 +1,4 @@ +check ok! +check ok! +check ok! +hello, world! diff --git a/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java b/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java index 48277aa8..d12c9057 100644 --- a/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java +++ b/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java @@ -55,7 +55,7 @@ import org.testng.annotations.Test; * Tests for JSR-223 script engine for Nashorn. * * @test - * @build jdk.nashorn.api.scripting.Window jdk.nashorn.api.scripting.WindowEventHandler jdk.nashorn.api.scripting.ScriptEngineTest + * @build jdk.nashorn.api.scripting.Window jdk.nashorn.api.scripting.WindowEventHandler jdk.nashorn.api.scripting.VariableArityTestInterface jdk.nashorn.api.scripting.ScriptEngineTest * @run testng jdk.nashorn.api.scripting.ScriptEngineTest */ public class ScriptEngineTest { @@ -906,4 +906,26 @@ public class ScriptEngineTest { fail(se.getMessage()); } } + + @Test + /** + * Tests whether invocation of a JavaScript method through a variable arity Java method will pass the vararg array. + * Both non-vararg and vararg JavaScript methods are tested. + * @throws ScriptException + */ + public void variableArityInterfaceTest() throws ScriptException { + final ScriptEngineManager m = new ScriptEngineManager(); + final ScriptEngine e = m.getEngineByName("nashorn"); + e.eval( + "function test1(i, strings) {" + + " return 'i == ' + i + ', strings instanceof java.lang.String[] == ' + (strings instanceof Java.type('java.lang.String[]')) + ', strings == ' + java.util.Arrays.toString(strings)" + + "}" + + "function test2() {" + + " return 'arguments[0] == ' + arguments[0] + ', arguments[1] instanceof java.lang.String[] == ' + (arguments[1] instanceof Java.type('java.lang.String[]')) + ', arguments[1] == ' + java.util.Arrays.toString(arguments[1])" + + "}" + ); + final VariableArityTestInterface itf = ((Invocable)e).getInterface(VariableArityTestInterface.class); + Assert.assertEquals(itf.test1(42, "a", "b"), "i == 42, strings instanceof java.lang.String[] == true, strings == [a, b]"); + Assert.assertEquals(itf.test2(44, "c", "d", "e"), "arguments[0] == 44, arguments[1] instanceof java.lang.String[] == true, arguments[1] == [c, d, e]"); + } } diff --git a/test/src/jdk/nashorn/api/scripting/VariableArityTestInterface.java b/test/src/jdk/nashorn/api/scripting/VariableArityTestInterface.java new file mode 100644 index 00000000..d3904522 --- /dev/null +++ b/test/src/jdk/nashorn/api/scripting/VariableArityTestInterface.java @@ -0,0 +1,31 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.api.scripting; + +public interface VariableArityTestInterface { + public String test1(int i, String... strings); + public String test2(int i, String... strings); +} diff --git a/test/src/jdk/nashorn/internal/runtime/JSTypeTest.java b/test/src/jdk/nashorn/internal/runtime/JSTypeTest.java index 6cf18d02..406ce1c3 100644 --- a/test/src/jdk/nashorn/internal/runtime/JSTypeTest.java +++ b/test/src/jdk/nashorn/internal/runtime/JSTypeTest.java @@ -105,4 +105,89 @@ public class JSTypeTest { // FIXME: add more number-to-string test cases // FIXME: add case for Object type (JSObject with getDefaultValue) } + + /** + * Test of JSType.toUint32(double) + */ + @Test + public void testToUint32() { + assertEquals(JSType.toUint32(+0.0), 0); + assertEquals(JSType.toUint32(-0.0), 0); + assertEquals(JSType.toUint32(Double.NaN), 0); + assertEquals(JSType.toUint32(Double.POSITIVE_INFINITY), 0); + assertEquals(JSType.toUint32(Double.NEGATIVE_INFINITY), 0); + assertEquals(JSType.toUint32(9223372036854775807.0d), 0); + assertEquals(JSType.toUint32(-9223372036854775807.0d), 0); + assertEquals(JSType.toUint32(1099511627776.0d), 0); + assertEquals(JSType.toUint32(-1099511627776.0d), 0); + assertEquals(JSType.toUint32(4294967295.0d), 4294967295l); + assertEquals(JSType.toUint32(4294967296.0d), 0); + assertEquals(JSType.toUint32(4294967297.0d), 1); + assertEquals(JSType.toUint32(-4294967295.0d), 1); + assertEquals(JSType.toUint32(-4294967296.0d), 0); + assertEquals(JSType.toUint32(-4294967297.0d), 4294967295l); + assertEquals(JSType.toUint32(4294967295.6d), 4294967295l); + assertEquals(JSType.toUint32(4294967296.6d), 0); + assertEquals(JSType.toUint32(4294967297.6d), 1); + assertEquals(JSType.toUint32(-4294967295.6d), 1); + assertEquals(JSType.toUint32(-4294967296.6d), 0); + assertEquals(JSType.toUint32(-4294967297.6d), 4294967295l); + } + + /** + * Test of JSType.toInt32(double) + */ + @Test + public void testToInt32() { + assertEquals(JSType.toInt32(+0.0), 0); + assertEquals(JSType.toInt32(-0.0), 0); + assertEquals(JSType.toInt32(Double.NaN), 0); + assertEquals(JSType.toInt32(Double.POSITIVE_INFINITY), 0); + assertEquals(JSType.toInt32(Double.NEGATIVE_INFINITY), 0); + assertEquals(JSType.toInt32(9223372036854775807.0d), 0); + assertEquals(JSType.toInt32(-9223372036854775807.0d), 0); + assertEquals(JSType.toInt32(1099511627776.0d), 0); + assertEquals(JSType.toInt32(-1099511627776.0d), 0); + assertEquals(JSType.toInt32(4294967295.0d), -1); + assertEquals(JSType.toInt32(4294967296.0d), 0); + assertEquals(JSType.toInt32(4294967297.0d), 1); + assertEquals(JSType.toInt32(-4294967295.0d), 1); + assertEquals(JSType.toInt32(-4294967296.0d), 0); + assertEquals(JSType.toInt32(-4294967297.d), -1); + assertEquals(JSType.toInt32(4294967295.6d), -1); + assertEquals(JSType.toInt32(4294967296.6d), 0); + assertEquals(JSType.toInt32(4294967297.6d), 1); + assertEquals(JSType.toInt32(-4294967295.6d), 1); + assertEquals(JSType.toInt32(-4294967296.6d), 0); + assertEquals(JSType.toInt32(-4294967297.6d), -1); + } + + /** + * Test of JSType.toUint16(double) + */ + @Test + public void testToUint16() { + assertEquals(JSType.toUint16(+0.0), 0); + assertEquals(JSType.toUint16(-0.0), 0); + assertEquals(JSType.toUint16(Double.NaN), 0); + assertEquals(JSType.toUint16(Double.POSITIVE_INFINITY), 0); + assertEquals(JSType.toUint16(Double.NEGATIVE_INFINITY), 0); + assertEquals(JSType.toUint16(9223372036854775807.0d), 0); + assertEquals(JSType.toUint16(-9223372036854775807.0d), 0); + assertEquals(JSType.toUint16(1099511627776.0d), 0); + assertEquals(JSType.toUint16(-1099511627776.0d), 0); + assertEquals(JSType.toUint16(4294967295.0d), 65535); + assertEquals(JSType.toUint16(4294967296.0d), 0); + assertEquals(JSType.toUint16(4294967297.0d), 1); + assertEquals(JSType.toUint16(-4294967295.0d), 1); + assertEquals(JSType.toUint16(-4294967296.0d), 0); + assertEquals(JSType.toUint16(-4294967297.0d), 65535); + assertEquals(JSType.toUint16(4294967295.6d), 65535); + assertEquals(JSType.toUint16(4294967296.6d), 0); + assertEquals(JSType.toUint16(4294967297.6d), 1); + assertEquals(JSType.toUint16(-4294967295.6d), 1); + assertEquals(JSType.toUint16(-4294967296.6d), 0); + assertEquals(JSType.toUint16(-4294967297.6d), 65535); + } + } diff --git a/test/src/jdk/nashorn/test/models/Jdk8011362TestSubject.java b/test/src/jdk/nashorn/test/models/Jdk8011362TestSubject.java new file mode 100644 index 00000000..1b179758 --- /dev/null +++ b/test/src/jdk/nashorn/test/models/Jdk8011362TestSubject.java @@ -0,0 +1,47 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.test.models; + +/** + * Test class used by JDK-8011362.js. + */ +public class Jdk8011362TestSubject { + // This is selected for overloaded("", null) + public String overloaded(String a, String b) { + return "overloaded(String, String)"; + } + + // This is selected for overloaded(0, null) + public String overloaded(Double a, Double b) { + return "overloaded(Double, Double)"; + } + + // This method is added to test that null will not match a primitive type, that is overloaded(0, null) will always + // select the (Double, Double) over (Double, double). + public String overloaded(Double a, double b) { + return "overloaded(Double, double)"; + } +} diff --git a/test/src/jdk/nashorn/test/models/VarArgConstructor.java b/test/src/jdk/nashorn/test/models/VarArgConstructor.java new file mode 100644 index 00000000..5f5da30e --- /dev/null +++ b/test/src/jdk/nashorn/test/models/VarArgConstructor.java @@ -0,0 +1,44 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.test.models; + +import java.util.List; + +public class VarArgConstructor { + private final String indicator; + + public VarArgConstructor(int x, boolean y, List<String> z) { + indicator = "non-vararg"; + } + + public VarArgConstructor(int x, boolean y, String... z) { + indicator = "vararg"; + } + + public String getIndicator() { + return indicator; + } +} diff --git a/tools/fxshell/jdk/nashorn/tools/FXShell.java b/tools/fxshell/jdk/nashorn/tools/FXShell.java new file mode 100644 index 00000000..df473189 --- /dev/null +++ b/tools/fxshell/jdk/nashorn/tools/FXShell.java @@ -0,0 +1,194 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.tools; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Application; +import javafx.stage.Stage; +import javax.script.Invocable; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import jdk.nashorn.api.scripting.NashornScriptEngineFactory; + +/** + * This shell is designed to launch a JavaFX application written in Nashorn JavaScript. + */ +public class FXShell extends Application { + /** + * Script engine manager to search. + */ + private ScriptEngineManager manager; + /** + * Nashorn script engine factory. + */ + private NashornScriptEngineFactory factory; + /** + * Main instance of Nashorn script engine. + */ + private ScriptEngine engine; + + /** + * Needed so that the FX launcher can create an instance of this class. + */ + public FXShell() { + } + + /** + * Main entry point. Never actually used. + * @param args Command lien arguments. + */ + public static void main(String[] args) { + launch(args); + } + + /* + * Application overrides. + */ + + @Override + public void init() throws Exception { + // Script engine manager to search. + this.manager = new ScriptEngineManager(); + + // Locate the Nashorn script engine factory. Needed for passing arguments. + for (ScriptEngineFactory engineFactory : this.manager.getEngineFactories()) { + if (engineFactory.getEngineName().equals("Oracle Nashorn") && engineFactory instanceof NashornScriptEngineFactory) { + this.factory = (NashornScriptEngineFactory)engineFactory; + } + } + + // If none located. + if (this.factory == null) { + System.err.println("Nashorn script engine not available"); + System.exit(1); + } + + // Get the command line and JNLP parameters. + final Parameters parameters = getParameters(); + + // To collect the script paths and command line arguments. + final List<String> paths = new ArrayList<>(); + final List<String> args = new ArrayList<>(); + + // Pull out relevant JNLP named parameters. + final Map<String, String> named = parameters.getNamed(); + for (Map.Entry<String, String> entry : named.entrySet()) { + final String key = entry.getKey(); + final String value = entry.getValue(); + + if ((key.equals("cp") || key.equals("classpath")) && value != null) { + args.add("-classpath"); + args.add(value); + } else if (key.equals("source") && value != null && value.toLowerCase().endsWith(".js")) { + paths.add(value); + } + } + + // Pull out relevant command line arguments. + boolean addNextArg = false; + boolean addAllArgs = false; + for (String parameter : parameters.getUnnamed()) { + if (addAllArgs || addNextArg) { + args.add(parameter); + addNextArg = false; + } else if (parameter.equals("--")) { + args.add(parameter); + addAllArgs = true; + } else if (parameter.startsWith("-")) { + args.add(parameter); + addNextArg = parameter.equals("-cp") || parameter.equals("-classpath"); + } else if (parameter.toLowerCase().endsWith(".js")) { + paths.add(parameter); + } + } + + // Create a Nashorn script engine with specified arguments. + engine = factory.getScriptEngine(args.toArray(new String[args.size()])); + + // Load initial scripts. + for (String path : paths) { + load(path); + } + + // Invoke users JavaScript init function if present. + try { + ((Invocable) engine).invokeFunction("init"); + } catch (NoSuchMethodException ex) { + // Presence of init is optional. + } + } + + @Override + public void start(Stage stage) throws Exception { + // Invoke users JavaScript start function if present. + try { + ((Invocable) engine).invokeFunction("start", stage); + } catch (NoSuchMethodException ex) { + // Presence of start is optional. + } + } + + @Override + public void stop() throws Exception { + // Invoke users JavaScript stop function if present. + try { + ((Invocable) engine).invokeFunction("stop"); + } catch (NoSuchMethodException ex) { + // Presence of stop is optional. + } + } + + /** + * Load and evaluate the specified JavaScript file. + * + * @param path Path to UTF-8 encoded JavaScript file. + * + * @return Last evaluation result (discarded.) + */ + private Object load(String path) { + try { + FileInputStream file = new FileInputStream(path); + InputStreamReader input = new InputStreamReader(file, "UTF-8"); + return engine.eval(input); + } catch (FileNotFoundException | UnsupportedEncodingException | ScriptException ex) { + ex.printStackTrace(); + } + + return null; + } +} |