# HG changeset patch # User lana # Date 1404250048 25200 # Node ID eb2479579f1e0ebaa0dcba45ea301800fb44e00d # Parent 28dd0c7beb3cad9cf95f17b4b5ad87eb447a4084# Parent b5a4e0ac31d183ffb7fbbb075874309cfd64c384 Merge diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/bin/jjsdebug.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/bin/jjsdebug.sh Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,25 @@ +#!/bin/sh +# +# Copyright (c) 2014, 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. +# + +$JAVA_HOME/bin/jjs -J-Djava.ext.dirs=`dirname $0`/../dist -J-agentlib:jdwp=transport=dt_socket,address=localhost:9009,server=y,suspend=y $* diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/make/build.xml --- a/nashorn/make/build.xml Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/make/build.xml Tue Jul 01 14:27:28 2014 -0700 @@ -125,8 +125,7 @@ encoding="${javac.encoding}" includeantruntime="false" fork="true"> - - + diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/make/nbproject/ide-targets.xml --- a/nashorn/make/nbproject/ide-targets.xml Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/make/nbproject/ide-targets.xml Tue Jul 01 14:27:28 2014 -0700 @@ -34,7 +34,7 @@ - + diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/make/project.properties --- a/nashorn/make/project.properties Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/make/project.properties Tue Jul 01 14:27:28 2014 -0700 @@ -279,6 +279,7 @@ -Dfile.encoding=UTF-8 \ -Duser.language=${run.test.user.language} \ -Duser.country=${run.test.user.country} \ + -Dnashorn.typeInfo.cacheDir=${build.dir}${file.separator}test${file.separator}type_info_cache \ ${jfr.args} \ -XX:+HeapDumpOnOutOfMemoryError diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java --- a/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java Tue Jul 01 14:27:28 2014 -0700 @@ -152,7 +152,7 @@ @SuppressWarnings("unused") private MethodHandle selectMethod(final Object[] args) throws NoSuchMethodException { - final Class[] argTypes = new Class[args.length]; + final Class[] argTypes = new Class[args.length]; for(int i = 0; i < argTypes.length; ++i) { final Object arg = args[i]; argTypes[i] = arg == null ? ClassString.NULL_CLASS : arg.getClass(); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java --- a/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java Tue Jul 01 14:27:28 2014 -0700 @@ -111,7 +111,7 @@ private final TypeBasedGuardingDynamicLinker[] linkers; private final List[] singletonLinkers; - @SuppressWarnings("unchecked") + @SuppressWarnings(value={"unchecked", "rawtypes"}) ClassToLinker(final TypeBasedGuardingDynamicLinker[] linkers) { this.linkers = linkers; singletonLinkers = new List[linkers.length]; @@ -120,6 +120,7 @@ } } + @SuppressWarnings("fallthrough") @Override protected List computeValue(final Class clazz) { List list = NO_LINKER; diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java --- a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java Tue Jul 01 14:27:28 2014 -0700 @@ -209,7 +209,7 @@ if (varNode.isFunctionDeclaration()) { symbol.setIsFunctionDeclaration(); } - return varNode.setName((IdentNode)ident.setSymbol(symbol)); + return varNode.setName(ident.setSymbol(symbol)); } return varNode; } @@ -217,7 +217,7 @@ } private IdentNode compilerConstantIdentifier(final CompilerConstants cc) { - return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc)); + return createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc)); } /** @@ -263,7 +263,7 @@ final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName()); assert nameSymbol != null; - return (VarNode)synthVar.setName((IdentNode)name.setSymbol(nameSymbol)).accept(this); + return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this); } private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) { @@ -522,7 +522,7 @@ final Symbol paramSymbol = body.getExistingSymbol(param.getName()); assert paramSymbol != null; assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags(); - newParams.add((IdentNode)param.setSymbol(paramSymbol)); + newParams.add(param.setSymbol(paramSymbol)); // parameters should not be slots for a function that uses variable arity signature if (isVarArg) { @@ -702,7 +702,7 @@ // If this is a declared variable or a function parameter, delete always fails (except for globals). final String name = ident.getName(); final Symbol symbol = ident.getSymbol(); - final boolean failDelete = strictMode || symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel()); + final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel()))); if (failDelete && symbol.isThis()) { return LiteralNode.newInstance(unaryNode, true).accept(this); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java --- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Tue Jul 01 14:27:28 2014 -0700 @@ -145,6 +145,7 @@ import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; import jdk.nashorn.internal.runtime.RewriteException; import jdk.nashorn.internal.runtime.Scope; +import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; @@ -210,6 +211,9 @@ * by reflection in class installation */ private final Compiler compiler; + /** Is the current code submitted by 'eval' call? */ + private final boolean evalCode; + /** Call site flags given to the code generator to be used for all generated call sites */ private final int callSiteFlags; @@ -264,6 +268,7 @@ CodeGenerator(final Compiler compiler, final int[] continuationEntryPoints) { super(new CodeGeneratorLexicalContext()); this.compiler = compiler; + this.evalCode = compiler.getSource().isEvalCode(); this.continuationEntryPoints = continuationEntryPoints; this.callSiteFlags = compiler.getScriptEnvironment()._callsite_flags; this.log = initLogger(compiler.getContext()); @@ -290,6 +295,14 @@ } /** + * Are we generating code for 'eval' code? + * @return true if currently compiled code is 'eval' code. + */ + boolean isEvalCode() { + return evalCode; + } + + /** * Load an identity node * * @param identNode an identity node to load @@ -1084,7 +1097,7 @@ closeBlockVariables(block); lc.releaseSlots(); - assert !method.isReachable() || lc.getUsedSlotCount() == method.getFirstTemp(); + assert !method.isReachable() || (lc.isFunctionBody() ? 0 : lc.getUsedSlotCount()) == method.getFirstTemp(); return block; } @@ -1277,13 +1290,26 @@ int argsCount; @Override void loadStack() { - loadExpressionAsObject(ident); // Type.OBJECT as foo() makes no sense if foo == 3 - method.dup(); + /** + * We want to load 'eval' to check if it is indeed global builtin eval. + * If this eval call is inside a 'with' statement, dyn:getMethod|getProp|getElem + * would be generated if ident is a "isFunction". But, that would result in a + * bound function from WithObject. We don't want that as bound function as that + * won't be detected as builtin eval. So, we make ident as "not a function" which + * results in "dyn:getProp|getElem|getMethod" being generated and so WithObject + * would return unbounded eval function. + * + * Example: + * + * var global = this; + * function func() { + * with({ eval: global.eval) { eval("var x = 10;") } + * } + */ + loadExpressionAsObject(ident.setIsNotFunction()); // Type.OBJECT as foo() makes no sense if foo == 3 globalIsEval(); method.ifeq(is_not_eval); - // We don't need ScriptFunction object for 'eval' - method.pop(); // Load up self (scope). method.loadCompilerConstant(SCOPE); final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); @@ -1303,6 +1329,8 @@ method._goto(invoke_direct_eval); method.label(is_not_eval); + // load this time but with dyn:getMethod|getProp|getElem + loadExpressionAsObject(ident); // Type.OBJECT as foo() makes no sense if foo == 3 // This is some scope 'eval' or global eval replaced by user // but not the built-in ECMAScript 'eval' function call method.loadNull(); @@ -1821,19 +1849,40 @@ method.storeCompilerConstant(ARGUMENTS); } - /** - * Should this code generator skip generating code for inner functions? If lazy compilation is on, or we're - * doing an on-demand ("just-in-time") compilation, then we aren't generating code for inner functions. - */ - private boolean compileOutermostOnly() { - return compiler.isOnDemandCompilation() || compiler.getScriptEnvironment()._lazy_compilation; + private boolean skipFunction(final FunctionNode functionNode) { + final ScriptEnvironment env = compiler.getScriptEnvironment(); + final boolean lazy = env._lazy_compilation; + final boolean onDemand = compiler.isOnDemandCompilation(); + + // If this is on-demand or lazy compilation, don't compile a nested (not topmost) function. + if((onDemand || lazy) && lc.getOutermostFunction() != functionNode) { + return true; + } + + // If lazy compiling with optimistic types, don't compile the program eagerly either. It will soon be + // invalidated anyway. In presence of a class cache, this further means that an obsoleted program version + // lingers around. Also, currently loading previously persisted optimistic types information only works if + // we're on-demand compiling a function, so with this strategy the :program method can also have the warmup + // benefit of using previously persisted types. + // NOTE that this means the first compiled class will effectively just have a :createProgramFunction method, and + // the RecompilableScriptFunctionData (RSFD) object in its constants array. It won't even have the :program + // method. This is by design. It does mean that we're wasting one compiler execution (and we could minimize this + // by just running it up to scope depth calculation, which creates the RSFDs and then this limited codegen). + // We could emit an initial separate compile unit with the initial version of :program in it to better utilize + // the compilation pipeline, but that would need more invasive changes, as currently the assumption that + // :program is emitted into the first compilation unit of the function lives in many places. + if(!onDemand && lazy && env._optimistic_types && functionNode.isProgram()) { + return true; + } + + return false; } @Override public boolean enterFunctionNode(final FunctionNode functionNode) { final int fnId = functionNode.getId(); - if (compileOutermostOnly() && lc.getOutermostFunction() != functionNode) { + if (skipFunction(functionNode)) { // In case we are not generating code for the function, we must create or retrieve the function object and // load it on the stack here. newFunctionObject(functionNode, false); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java --- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Tue Jul 01 14:27:28 2014 -0700 @@ -173,7 +173,18 @@ @Override FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { final CompileUnit outermostCompileUnit = compiler.addCompileUnit(0L); - final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn, true); + + FunctionNode newFunctionNode; + + //ensure elementTypes, postsets and presets exist for splitter and arraynodes + newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor(new LexicalContext()) { + @Override + public LiteralNode leaveLiteralNode(final LiteralNode literalNode) { + return literalNode.initialize(lc); + } + }); + + newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true); assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit; assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName()); @@ -374,7 +385,7 @@ assert newUnit != null; newArrayUnits.add(new ArrayUnit(newUnit, au.getLo(), au.getHi())); } - aln.setUnits(newArrayUnits); + return aln.setUnits(lc, newArrayUnits); } return node; } @@ -421,7 +432,9 @@ compiler.getLogger().fine("Starting bytecode generation for ", quote(fn.getName()), " - restOf=", phases.isRestOfCompilation()); final CodeGenerator codegen = new CodeGenerator(compiler, phases.isRestOfCompilation() ? compiler.getContinuationEntryPoints() : null); try { - newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen); + // Explicitly set BYTECODE_GENERATED here; it can not be set in case of skipping codegen for :program + // in the lazy + optimistic world. See CodeGenerator.skipFunction(). + newFunctionNode = ((FunctionNode)newFunctionNode.accept(codegen)).setState(null, BYTECODE_GENERATED); codegen.generateScopeCalls(); } catch (final VerifyError e) { if (senv._verify_code || senv._print_code) { @@ -489,7 +502,7 @@ Class rootClass = null; long length = 0L; - final CodeInstaller codeInstaller = compiler.getCodeInstaller(); + final CodeInstaller codeInstaller = compiler.getCodeInstaller(); final Map bytecode = compiler.getBytecode(); @@ -514,12 +527,10 @@ final Object[] constants = compiler.getConstantData().toArray(); codeInstaller.initialize(installedClasses.values(), compiler.getSource(), constants); - // index recompilable script function datas in the constant pool - final Map rfns = new IdentityHashMap<>(); + // initialize transient fields on recompilable script function data for (final Object constant: constants) { if (constant instanceof RecompilableScriptFunctionData) { - final RecompilableScriptFunctionData rfn = (RecompilableScriptFunctionData)constant; - rfns.put(rfn, rfn); + ((RecompilableScriptFunctionData)constant).initTransients(compiler.getSource(), codeInstaller); } } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/Compiler.java --- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Tue Jul 01 14:27:28 2014 -0700 @@ -46,10 +46,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; 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; @@ -123,6 +123,11 @@ private final Map invalidatedProgramPoints; /** + * Descriptor of the location where we write the type information after compilation. + */ + private final Object typeInformationFile; + + /** * Compile unit name of first compile unit - this prefix will be used for all * classes that a compilation generates. */ @@ -317,7 +322,7 @@ final Source source, final String sourceURL, final boolean isStrict) { - this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null); + this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null, null); } /** @@ -333,6 +338,7 @@ * @param compiledFunction compiled function, if any * @param types parameter and return value type information, if any is known * @param invalidatedProgramPoints invalidated program points for recompilation + * @param typeInformationFile descriptor of the location where type information is persisted * @param continuationEntryPoints continuation entry points for restof method * @param runtimeScope runtime scope for recompilation type lookup in {@code TypeEvaluator} */ @@ -347,6 +353,7 @@ final RecompilableScriptFunctionData compiledFunction, final TypeMap types, final Map invalidatedProgramPoints, + final Object typeInformationFile, final int[] continuationEntryPoints, final ScriptObject runtimeScope) { this.context = context; @@ -363,6 +370,7 @@ this.compiledFunction = compiledFunction; this.types = types; this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap() : invalidatedProgramPoints; + this.typeInformationFile = typeInformationFile; this.continuationEntryPoints = continuationEntryPoints == null ? null: continuationEntryPoints.clone(); this.typeEvaluator = new TypeEvaluator(this, runtimeScope); this.firstCompileUnitName = firstCompileUnitName(); @@ -457,6 +465,16 @@ invalidatedProgramPoints.put(programPoint, type); } + + /** + * Returns a copy of this compiler's current mapping of invalidated optimistic program points to their types. The + * copy is not live with regard to changes in state in this compiler instance, and is mutable. + * @return a copy of this compiler's current mapping of invalidated optimistic program points to their types. + */ + public Map getInvalidatedProgramPoints() { + return invalidatedProgramPoints == null ? null : new TreeMap<>(invalidatedProgramPoints); + } + TypeMap getTypeMap() { return types; } @@ -513,6 +531,10 @@ time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L); } + if(typeInformationFile != null && !phases.isRestOfCompilation()) { + OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints); + } + log.unindent(); if (info) { diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java --- a/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java Tue Jul 01 14:27:28 2014 -0700 @@ -62,6 +62,8 @@ /** call site flags to be used for invocations */ private final int callSiteFlags; + /** are we creating this field object from 'eval' code? */ + private final boolean evalCode; /** * Constructor @@ -88,7 +90,7 @@ FieldObjectCreator(final CodeGenerator codegen, final List> tuples, final boolean isScope, final boolean hasArguments) { super(codegen, tuples, isScope, hasArguments); this.callSiteFlags = codegen.getCallSiteFlags(); - + this.evalCode = codegen.isEvalCode(); countFields(); findClass(); } @@ -153,7 +155,7 @@ @Override protected PropertyMap makeMap() { assert propertyMap == null : "property map already initialized"; - propertyMap = newMapCreator(fieldObjectClass).makeFieldMap(hasArguments(), fieldCount, paddedFieldCount); + propertyMap = newMapCreator(fieldObjectClass).makeFieldMap(hasArguments(), fieldCount, paddedFieldCount, evalCode); return propertyMap; } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java --- a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java Tue Jul 01 14:27:28 2014 -0700 @@ -141,7 +141,7 @@ paramTypeList.add(paramType.getTypeClass()); } - this.methodType = MH.type(returnType.getTypeClass(), paramTypeList.toArray(new Class[paramTypes.length])); + this.methodType = MH.type(returnType.getTypeClass(), paramTypeList.toArray(new Class[paramTypes.length])); } /** diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java --- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Tue Jul 01 14:27:28 2014 -0700 @@ -25,10 +25,12 @@ package jdk.nashorn.internal.codegen; +import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.ir.Expression.isAlwaysFalse; import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashSet; @@ -63,7 +65,6 @@ 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.LocalVariableConversion; import jdk.nashorn.internal.ir.LoopNode; import jdk.nashorn.internal.ir.Node; @@ -72,6 +73,7 @@ import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.RuntimeNode.Request; import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.TernaryNode; @@ -356,6 +358,8 @@ private boolean reachable = true; // Return type of the function private Type returnType = Type.UNKNOWN; + // Synthetic return node that we must insert at the end of the function if it's end is reachable. + private ReturnNode syntheticReturn; // Topmost current split node (if any) private SplitNode topSplit; @@ -583,7 +587,11 @@ } } setCompilerConstantAsObject(functionNode, CompilerConstants.THIS); - if(functionNode.needsParentScope()) { + + // TODO: coarse-grained. If we wanted to solve it completely precisely, + // we'd also need to push/pop its type when handling WithNode (so that + // it can go back to undefined after a 'with' block. + if(functionNode.hasScopeBlock() || functionNode.needsParentScope()) { setCompilerConstantAsObject(functionNode, CompilerConstants.SCOPE); } if(functionNode.needsCallee()) { @@ -841,6 +849,10 @@ @Override public boolean enterThrowNode(final ThrowNode throwNode) { + if(!reachable) { + return false; + } + throwNode.getExpression().accept(this); jumpToCatchBlock(throwNode); doesNotContinueSequentially(); @@ -1027,6 +1039,15 @@ @Override public Node leaveBlock(final Block block) { if(lc.isFunctionBody()) { + if(reachable) { + // reachable==true means we can reach the end of the function without an explicit return statement. We + // need to insert a synthetic one then. This logic used to be in Lower.leaveBlock(), but Lower's + // reachability analysis (through Terminal.isTerminal() flags) is not precise enough so + // Lower$BlockLexicalContext.afterSetStatements will sometimes think the control flow terminates even + // when it didn't. Example: function() { switch((z)) { default: {break; } throw x; } }. + createSyntheticReturn(block); + assert !reachable; + } // We must calculate the return type here (and not in leaveFunctionNode) as it can affect the liveness of // the :return symbol and thus affect conversion type liveness calculations for it. calculateReturnType(); @@ -1085,6 +1106,23 @@ retSymbol.setNeedsSlot(true); } } + + private void createSyntheticReturn(final Block body) { + final FunctionNode functionNode = lc.getCurrentFunction(); + final long token = functionNode.getToken(); + final int finish = functionNode.getFinish(); + final List statements = body.getStatements(); + final int lineNumber = statements.isEmpty() ? functionNode.getLineNumber() : statements.get(statements.size() - 1).getLineNumber(); + final IdentNode returnExpr; + if(functionNode.isProgram()) { + returnExpr = new IdentNode(token, finish, RETURN.symbolName()).setSymbol(getCompilerConstantSymbol(functionNode, RETURN)); + } else { + returnExpr = null; + } + syntheticReturn = new ReturnNode(lineNumber, token, finish, returnExpr); + syntheticReturn.accept(this); + } + /** * Leave a breakable node. If there's a join point associated with its break label (meaning there was at least one * break statement to the end of the node), insert the join point into the flow. @@ -1158,7 +1196,9 @@ } else if(binaryNode.isOptimisticUndecidedType()) { // At this point, we can assign a static type to the optimistic binary ADD operator as now we know // the types of its operands. - return binaryNode.setType(Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType())); + final Type type = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); + // Use Type.CHARSEQUENCE instead of Type.STRING to avoid conversion of ConsStrings to Strings. + return binaryNode.setType(type.equals(Type.STRING) ? Type.CHARSEQUENCE : type); } return binaryNode; } @@ -1174,6 +1214,16 @@ } @Override + public Node leaveBlock(final Block block) { + if(inOuterFunction && syntheticReturn != null && lc.isFunctionBody()) { + final ArrayList stmts = new ArrayList<>(block.getStatements()); + stmts.add((ReturnNode)syntheticReturn.accept(this)); + return block.setStatements(lc, stmts); + } + return super.leaveBlock(block); + } + + @Override public Node leaveFunctionNode(final FunctionNode nestedFunctionNode) { inOuterFunction = true; final FunctionNode newNestedFunction = (FunctionNode)nestedFunctionNode.accept( @@ -1207,10 +1257,10 @@ @Override public Node leaveLiteralNode(final LiteralNode literalNode) { - if(literalNode instanceof ArrayLiteralNode) { - ((ArrayLiteralNode)literalNode).analyze(); - } - return literalNode; + //for e.g. ArrayLiteralNodes the initial types may have been narrowed due to the + //introduction of optimistic behavior - hence ensure that all literal nodes are + //reinitialized + return literalNode.initialize(lc); } @Override diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/Lower.java --- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java Tue Jul 01 14:27:28 2014 -0700 @@ -75,7 +75,6 @@ import jdk.nashorn.internal.runtime.CodeInstaller; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.JSType; -import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; @@ -160,30 +159,6 @@ } @Override - 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 - - - if (lc.isFunctionBody()) { - final FunctionNode currentFunction = lc.getCurrentFunction(); - final boolean isProgram = currentFunction.isProgram(); - final Statement last = lc.getLastStatement(); - final ReturnNode returnNode = new ReturnNode( - last == null ? currentFunction.getLineNumber() : last.getLineNumber(), //TODO? - currentFunction.getToken(), - currentFunction.getFinish(), - isProgram ? - compilerConstant(RETURN) : - LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)); - - returnNode.accept(this); - } - - return block; - } - - @Override public boolean enterBreakNode(final BreakNode breakNode) { addStatement(breakNode); return false; diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java --- a/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java Tue Jul 01 14:27:28 2014 -0700 @@ -63,13 +63,13 @@ /** * Constructs a property map based on a set of fields. * - * @param hasArguments does the created object have an "arguments" property + * @param hasArguments does the created object have an "arguments" property * @param fieldCount Number of fields in use. - * @param fieldMaximum Number of fields available. - * + * @param fieldMaximum Number of fields available. + * @param evalCode is this property map created for 'eval' code? * @return New map populated with accessor properties. */ - PropertyMap makeFieldMap(final boolean hasArguments, final int fieldCount, final int fieldMaximum) { + PropertyMap makeFieldMap(final boolean hasArguments, final int fieldCount, final int fieldMaximum, final boolean evalCode) { final List properties = new ArrayList<>(); assert tuples != null; @@ -79,7 +79,7 @@ final Class initialType = tuple.getValueType(); if (symbol != null && !isValidArrayIndex(getArrayIndex(key))) { - final int flags = getPropertyFlags(symbol, hasArguments); + final int flags = getPropertyFlags(symbol, hasArguments, evalCode); final Property property = new AccessorProperty( key, flags, @@ -104,7 +104,7 @@ //TODO initial type is object here no matter what. Is that right? if (symbol != null && !isValidArrayIndex(getArrayIndex(key))) { - final int flags = getPropertyFlags(symbol, hasArguments); + final int flags = getPropertyFlags(symbol, hasArguments, false); properties.add( new SpillProperty( key, @@ -124,7 +124,7 @@ * * @return flags to use for fields */ - static int getPropertyFlags(final Symbol symbol, final boolean hasArguments) { + static int getPropertyFlags(final Symbol symbol, final boolean hasArguments, final boolean evalCode) { int flags = 0; if (symbol.isParam()) { @@ -135,7 +135,13 @@ flags |= Property.HAS_ARGUMENTS; } - if (symbol.isScope()) { + // See ECMA 5.1 10.5 Declaration Binding Instantiation. + // Step 2 If code is eval code, then let configurableBindings + // be true else let configurableBindings be false. + // We have to make vars, functions declared in 'eval' code + // configurable. But vars, functions from any other code is + // not configurable. + if (symbol.isScope() && !evalCode) { flags |= Property.NOT_CONFIGURABLE; } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2010, 2014, 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.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URL; +import java.security.AccessController; +import java.security.MessageDigest; +import java.security.PrivilegedAction; +import java.sql.Date; +import java.text.SimpleDateFormat; +import java.util.Base64; +import java.util.Map; +import java.util.TreeMap; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; +import jdk.nashorn.internal.runtime.Source; +import jdk.nashorn.internal.runtime.logging.DebugLogger; +import jdk.nashorn.internal.runtime.options.Options; + +/** + * Static utility that encapsulates persistence of decompilation information for functions. Normally, the type info + * persistence feature is enabled and operates in an operating-system specific per-user cache directory. You can + * override the directory by specifying it in the {@code nashorn.typeInfo.cacheDir} directory. Also, you can disable the + * type info persistence altogether by specifying the {@code nashorn.typeInfo.disabled} system property. + */ +public final class OptimisticTypesPersistence { + private static final File cacheDir = createCacheDir(); + // In-process locks to make sure we don't have a cross-thread race condition manipulating any file. + private static final Object[] locks = cacheDir == null ? null : createLockArray(); + + // Only report one read/write error every minute + private static final long ERROR_REPORT_THRESHOLD = 60000L; + + private static volatile long lastReportedError; + + /** + * Retrieves an opaque descriptor for the persistence location for a given function. It should be passed to + * {@link #load(Object)} and {@link #store(Object, Map)} methods. + * @param source the source where the function comes from + * @param functionId the unique ID number of the function within the source + * @param paramTypes the types of the function parameters (as persistence is per parameter type specialization). + * @return an opaque descriptor for the persistence location. Can be null if persistence is disabled. + */ + public static Object getLocationDescriptor(final Source source, final int functionId, final Type[] paramTypes) { + if(cacheDir == null) { + return null; + } + final StringBuilder b = new StringBuilder(48); + // Base64-encode the digest of the source, and append the function id. + b.append(Base64.getUrlEncoder().encodeToString(source.getDigest())).append('-').append(functionId); + // Finally, if this is a parameter-type specialized version of the function, add the parameter types to the file + // name. + if(paramTypes != null && paramTypes.length > 0) { + b.append('-'); + for(final Type t: paramTypes) { + b.append(t.getBytecodeStackType()); + } + } + return new LocationDescriptor(new File(cacheDir, b.toString())); + } + + private static final class LocationDescriptor { + private final File file; + + LocationDescriptor(final File file) { + this.file = file; + } + } + + + /** + * Stores the map of optimistic types for a given function. + * @param locationDescriptor the opaque persistence location descriptor, retrieved by calling + * {@link #getLocationDescriptor(Source, int, Type[])}. + * @param optimisticTypes the map of optimistic types. + */ + @SuppressWarnings("resource") + public static void store(final Object locationDescriptor, final Map optimisticTypes) { + if(locationDescriptor == null) { + return; + } + final File file = ((LocationDescriptor)locationDescriptor).file; + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + synchronized(getFileLock(file)) { + try (final FileOutputStream out = new FileOutputStream(file);) { + out.getChannel().lock(); // lock exclusive + final DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(out)); + dout.writeInt(optimisticTypes.size()); + for(Map.Entry e: optimisticTypes.entrySet()) { + dout.writeInt(e.getKey()); + final byte typeChar; + final Type type = e.getValue(); + if(type == Type.OBJECT) { + typeChar = 'L'; + } else if(type == Type.NUMBER) { + typeChar = 'D'; + } else if(type == Type.LONG) { + typeChar = 'J'; + } else { + throw new AssertionError(); + } + dout.write(typeChar); + } + dout.flush(); + } catch(final Exception e) { + reportError("write", file, e); + } + } + return null; + } + }); + } + + /** + * Loads the map of optimistic types for a given function. + * @param locationDescriptor the opaque persistence location descriptor, retrieved by calling + * {@link #getLocationDescriptor(Source, int, Type[])}. + * @return the map of optimistic types, or null if persisted type information could not be retrieved. + */ + @SuppressWarnings("resource") + public static Map load(final Object locationDescriptor) { + if (locationDescriptor == null) { + return null; + } + final File file = ((LocationDescriptor)locationDescriptor).file; + + return AccessController.doPrivileged(new PrivilegedAction>() { + @Override + public Map run() { + try { + if(!file.isFile()) { + return null; + } + synchronized(getFileLock(file)) { + try (final FileInputStream in = new FileInputStream(file);) { + in.getChannel().lock(0, Long.MAX_VALUE, true); // lock shared + final DataInputStream din = new DataInputStream(new BufferedInputStream(in)); + final Map map = new TreeMap<>(); + final int size = din.readInt(); + for(int i = 0; i < size; ++i) { + final int pp = din.readInt(); + final int typeChar = din.read(); + final Type type; + switch(typeChar) { + case 'L': type = Type.OBJECT; break; + case 'D': type = Type.NUMBER; break; + case 'J': type = Type.LONG; break; + default: throw new AssertionError(); + } + map.put(pp, type); + } + return map; + } + } + } catch (final Exception e) { + reportError("read", file, e); + return null; + } + } + }); + } + + private static void reportError(final String msg, final File file, final Exception e) { + final long now = System.currentTimeMillis(); + if(now - lastReportedError > ERROR_REPORT_THRESHOLD) { + getLogger().warning(String.format("Failed to %s %s", msg, file), e); + lastReportedError = now; + } + } + + private static File createCacheDir() { + if(Options.getBooleanProperty("nashorn.typeInfo.disabled")) { + return null; + } + try { + return createCacheDirPrivileged(); + } catch(final Exception e) { + getLogger().warning("Failed to create cache dir", e); + return null; + } + } + + private static File createCacheDirPrivileged() { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public File run() { + final String explicitDir = System.getProperty("nashorn.typeInfo.cacheDir"); + final File dir; + if(explicitDir != null) { + dir = new File(explicitDir); + } else { + // When no directory is explicitly specified, get an operating system specific cache directory, + // and create "com.oracle.java.NashornTypeInfo" in it. + dir = new File(getCacheDirBase(), "com.oracle.java.NashornTypeInfo"); + } + final String versionDirName; + try { + versionDirName = getVersionDirName(); + } catch(Exception e) { + getLogger().warning("Failed to calculate version dir name", e); + return null; + } + final File versionDir = new File(dir, versionDirName); + versionDir.mkdirs(); + if(versionDir.isDirectory()) { + getLogger().info("Optimistic type persistence directory is " + versionDir); + return versionDir; + } + getLogger().warning("Could not create optimistic type persistence directory " + versionDir); + return null; + } + }); + } + + /** + * Returns an operating system specific root directory for cache files. + * @return an operating system specific root directory for cache files. + */ + private static File getCacheDirBase() { + final String os = System.getProperty("os.name", "generic"); + if("Mac OS X".equals(os)) { + // Mac OS X stores caches in ~/Library/Caches + return new File(new File(System.getProperty("user.home"), "Library"), "Caches"); + } else if(os.startsWith("Windows")) { + // On Windows, temp directory is the best approximation of a cache directory, as its contents persist across + // reboots and various cleanup utilities know about it. java.io.tmpdir normally points to a user-specific + // temp directory, %HOME%\LocalSettings\Temp. + return new File(System.getProperty("java.io.tmpdir")); + } else { + // In all other cases we're presumably dealing with a UNIX flavor (Linux, Solaris, etc.); "~/.cache" + return new File(System.getProperty("user.home"), ".cache"); + } + } + + /** + * In order to ensure that changes in Nashorn code don't cause corruption in the data, we'll create a + * per-code-version directory. Normally, this will create the SHA-1 digest of the nashorn.jar. In case the classpath + * for nashorn is local directory (e.g. during development), this will create the string "dev-" followed by the + * timestamp of the most recent .class file. + * @return + */ + private static String getVersionDirName() throws Exception { + final URL url = OptimisticTypesPersistence.class.getResource(""); + final String protocol = url.getProtocol(); + if(protocol.equals("jar")) { + // Normal deployment: nashorn.jar + final String jarUrlFile = url.getFile(); + final String filePath = jarUrlFile.substring(0, jarUrlFile.indexOf('!')); + final URL file = new URL(filePath); + try (final InputStream in = file.openStream()) { + final byte[] buf = new byte[128*1024]; + final MessageDigest digest = MessageDigest.getInstance("SHA-1"); + for(;;) { + final int l = in.read(buf); + if(l == -1) { + return Base64.getUrlEncoder().encodeToString(digest.digest()); + } + digest.update(buf, 0, l); + } + } + } else if(protocol.equals("file")) { + // Development + final String fileStr = url.getFile(); + final String className = OptimisticTypesPersistence.class.getName(); + final int packageNameLen = className.lastIndexOf('.'); + final String dirStr = fileStr.substring(0, fileStr.length() - packageNameLen - 1); + final File dir = new File(dirStr); + return "dev-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(getLastModifiedClassFile(dir, 0L))); + } else { + throw new AssertionError(); + } + } + + private static long getLastModifiedClassFile(final File dir, final long max) { + long currentMax = max; + for(File f: dir.listFiles()) { + if(f.getName().endsWith(".class")) { + final long lastModified = f.lastModified(); + if(lastModified > currentMax) { + currentMax = lastModified; + } + } else if(f.isDirectory()) { + final long lastModified = getLastModifiedClassFile(f, currentMax); + if(lastModified > currentMax) { + currentMax = lastModified; + } + } + } + return currentMax; + } + + private static Object[] createLockArray() { + final Object[] lockArray = new Object[Runtime.getRuntime().availableProcessors() * 2]; + for(int i = 0; i < lockArray.length; ++i) { + lockArray[i] = new Object(); + } + return lockArray; + } + + private static Object getFileLock(final File file) { + return locks[(file.hashCode() & Integer.MAX_VALUE) % locks.length]; + } + + private static DebugLogger getLogger() { + try { + return Context.getContext().getLogger(RecompilableScriptFunctionData.class); + } catch (final Exception e) { + e.printStackTrace(); + return DebugLogger.DISABLED_LOGGER; + } + } +} diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/Splitter.java --- a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java Tue Jul 01 14:27:28 2014 -0700 @@ -307,7 +307,7 @@ units.add(new ArrayUnit(unit, lo, postsets.length)); } - arrayLiteralNode.setUnits(units); + return arrayLiteralNode.setUnits(lc, units); } return literal; diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/TypeEvaluator.java --- a/nashorn/src/jdk/nashorn/internal/codegen/TypeEvaluator.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/TypeEvaluator.java Tue Jul 01 14:27:28 2014 -0700 @@ -25,6 +25,10 @@ package jdk.nashorn.internal.codegen; +import static jdk.nashorn.internal.runtime.Property.NOT_CONFIGURABLE; +import static jdk.nashorn.internal.runtime.Property.NOT_ENUMERABLE; +import static jdk.nashorn.internal.runtime.Property.NOT_WRITABLE; + import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.Expression; @@ -43,8 +47,8 @@ * Used during recompilation. */ final class TypeEvaluator { - final Compiler compiler; - final ScriptObject runtimeScope; + private final Compiler compiler; + private final ScriptObject runtimeScope; TypeEvaluator(final Compiler compiler, final ScriptObject runtimeScope) { this.compiler = compiler; @@ -123,7 +127,7 @@ " scope="+runtimeScope; if (runtimeScope.findProperty(symbolName, false) == null) { - runtimeScope.set(symbolName, ScriptRuntime.UNDEFINED, true); + runtimeScope.addOwnProperty(symbolName, NOT_WRITABLE | NOT_ENUMERABLE | NOT_CONFIGURABLE, ScriptRuntime.UNDEFINED); } } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java --- a/nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java Tue Jul 01 14:27:28 2014 -0700 @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.NoSuchElementException; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.runtime.ScriptFunction; @@ -61,6 +62,20 @@ this.needsCallee = needsCallee; } + /** + * Returns the array of parameter types for a particular function node + * @param functionNodeId the ID of the function node + * @return an array of parameter types + * @throws NoSuchElementException if the type map has no mapping for the requested function + */ + public Type[] getParameterTypes(final int functionNodeId) { + final Type[] paramTypes = paramTypeMap.get(functionNodeId); + if (paramTypes == null) { + throw new NoSuchElementException(Integer.toString(functionNodeId)); + } + return paramTypes.clone(); + } + MethodType getCallSiteType(final FunctionNode functionNode) { final Type[] types = paramTypeMap.get(functionNode.getId()); if (types == null) { diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java --- a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java Tue Jul 01 14:27:28 2014 -0700 @@ -173,7 +173,6 @@ if (functionNode == topFunction) { // the function being weighted; descend into its statements return true; -// functionNode.visitStatements(this); } // just a reference to inner function from outer function weight += FUNC_EXPR_WEIGHT; diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/types/ObjectType.java --- a/nashorn/src/jdk/nashorn/internal/codegen/types/ObjectType.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/types/ObjectType.java Tue Jul 01 14:27:28 2014 -0700 @@ -171,6 +171,8 @@ invokestatic(method, JSType.TO_BOOLEAN); } else if (to.isString()) { invokestatic(method, JSType.TO_PRIMITIVE_TO_STRING); + } else if (to.isCharSequence()) { + invokestatic(method, JSType.TO_PRIMITIVE_TO_CHARSEQUENCE); } else { throw new UnsupportedOperationException("Illegal conversion " + this + " -> " + to + " " + isString() + " " + toString); } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/codegen/types/Type.java --- a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java Tue Jul 01 14:27:28 2014 -0700 @@ -417,6 +417,15 @@ } /** + * Determines whether a type is a CHARSEQUENCE type used internally strings + * + * @return true if CharSequence (internal string) type, false otherwise + */ + public boolean isCharSequence() { + return this.equals(Type.CHARSEQUENCE); + } + + /** * Determine if two types are equivalent, i.e. need no conversion * * @param type the second type to check @@ -800,6 +809,13 @@ public static final Type STRING = putInCache(new ObjectType(String.class)); /** + * This is the CharSequence singleton used to represent JS strings internally + * (either a {@code java.lang.String} or {@code jdk.nashorn.internal.runtime.ConsString}. + */ + public static final Type CHARSEQUENCE = putInCache(new ObjectType(CharSequence.class)); + + + /** * This is the object singleton, used for all object types */ public static final Type OBJECT = putInCache(new ObjectType()); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/Block.java --- a/nashorn/src/jdk/nashorn/internal/ir/Block.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/Block.java Tue Jul 01 14:27:28 2014 -0700 @@ -41,7 +41,7 @@ * IR representation for a list of statements. */ @Immutable -public class Block extends Node implements BreakableNode, Flags { +public class Block extends Node implements BreakableNode, Terminal, Flags { /** List of statements */ protected final List statements; @@ -231,6 +231,11 @@ return flags; } + /** + * Is this a terminal block, i.e. does it end control flow like ending with a throw or return? + * + * @return true if this node statement is terminal + */ @Override public boolean isTerminal() { return getFlag(IS_TERMINAL); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/CaseNode.java --- a/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java Tue Jul 01 14:27:28 2014 -0700 @@ -36,7 +36,7 @@ * Case nodes are not BreakableNodes, but the SwitchNode is */ @Immutable -public final class CaseNode extends Node implements JoinPredecessor, Labels { +public final class CaseNode extends Node implements JoinPredecessor, Labels, Terminal { /** Test expression. */ private final Expression test; @@ -77,6 +77,11 @@ this.conversion = conversion; } + /** + * Is this a terminal case node, i.e. does it end control flow like having a throw or return? + * + * @return true if this node statement is terminal + */ @Override public boolean isTerminal() { return body.isTerminal(); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/ExpressionStatement.java --- a/nashorn/src/jdk/nashorn/internal/ir/ExpressionStatement.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/ExpressionStatement.java Tue Jul 01 14:27:28 2014 -0700 @@ -57,11 +57,6 @@ } @Override - public boolean isTerminal() { - return expression.isTerminal(); - } - - @Override public Node accept(final NodeVisitor visitor) { if (visitor.enterExpressionStatement(this)) { return visitor.leaveExpressionStatement(setExpression((Expression)expression.accept(visitor))); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/IdentNode.java --- a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java Tue Jul 01 14:27:28 2014 -0700 @@ -110,7 +110,7 @@ * @return a temporary identifier for the symbol. */ public static IdentNode createInternalIdentifier(final Symbol symbol) { - return (IdentNode)new IdentNode(Token.toDesc(TokenType.IDENT, 0, 0), 0, symbol.getName()).setSymbol(symbol); + return new IdentNode(Token.toDesc(TokenType.IDENT, 0, 0), 0, symbol.getName()).setSymbol(symbol); } @Override @@ -180,7 +180,7 @@ * @param symbol the symbol * @return new node */ - public Expression setSymbol(final Symbol symbol) { + public IdentNode setSymbol(final Symbol symbol) { if (this.symbol == symbol) { return this; } @@ -280,6 +280,17 @@ return new IdentNode(this, name, type, flags | FUNCTION, programPoint, conversion); } + /** + * Mark this node as not being the callee operand of a {@link CallNode}. + * @return an ident node identical to this one in all aspects except with its function flag unset. + */ + public IdentNode setIsNotFunction() { + if (! isFunction()) { + return this; + } + return new IdentNode(this, name, type, flags & ~FUNCTION, programPoint, conversion); + } + @Override public int getProgramPoint() { return programPoint; diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java --- a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java Tue Jul 01 14:27:28 2014 -0700 @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.function.Function; + import jdk.nashorn.internal.codegen.CompileUnit; import jdk.nashorn.internal.codegen.types.ArrayType; import jdk.nashorn.internal.codegen.types.Type; @@ -87,6 +88,17 @@ } /** + * Initialization setter, if required for immutable state. This is used for + * things like ArrayLiteralNodes that need to carry state for the splitter. + * Default implementation is just a nop. + * @param lc lexical context + * @return new literal node with initialized state, or same if nothing changed + */ + public LiteralNode initialize(final LexicalContext lc) { + return this; + } + + /** * Check if the literal value is null * @return true if literal value is null */ @@ -573,24 +585,26 @@ /** * Array literal node class. */ + @Immutable public static final class ArrayLiteralNode extends LiteralNode implements LexicalContextNode { /** Array element type. */ - private Type elementType; + private final Type elementType; /** Preset constant array. */ - private Object presets; + private final Object presets; /** Indices of array elements requiring computed post sets. */ - private int[] postsets; + private final int[] postsets; - private List units; + /** Sub units with indexes ranges, in which to split up code generation, for large literals */ + private final List units; /** * An ArrayUnit is a range in an ArrayLiteral. ArrayLiterals can * be split if they are too large, for bytecode generation reasons */ - public static class ArrayUnit { + public static final class ArrayUnit { /** Compile unit associated with the postsets range. */ private final CompileUnit compileUnit; @@ -634,6 +648,150 @@ } } + private static final class ArrayLiteralInitializer { + + static ArrayLiteralNode initialize(final ArrayLiteralNode node) { + final Type elementType = computeElementType(node.value, node.elementType); + final int[] postsets = computePostsets(node.value); + final Object presets = computePresets(node.value, elementType, postsets); + return new ArrayLiteralNode(node, node.value, elementType, postsets, presets, node.units); + } + + private static Type computeElementType(final Expression[] value, final Type elementType) { + Type widestElementType = Type.INT; + + for (final Expression elem : value) { + if (elem == null) { + widestElementType = widestElementType.widest(Type.OBJECT); //no way to represent undefined as number + break; + } + + final Type type = elem.getType().isUnknown() ? Type.OBJECT : elem.getType(); + if (type.isBoolean()) { + //TODO fix this with explicit boolean types + widestElementType = widestElementType.widest(Type.OBJECT); + break; + } + + widestElementType = widestElementType.widest(type); + if (widestElementType.isObject()) { + break; + } + } + return widestElementType; + } + + private static int[] computePostsets(final Expression[] value) { + final int[] computed = new int[value.length]; + int nComputed = 0; + + for (int i = 0; i < value.length; i++) { + final Expression element = value[i]; + if (element == null || objectAsConstant(element) == POSTSET_MARKER) { + computed[nComputed++] = i; + } + } + return Arrays.copyOf(computed, nComputed); + } + + private static boolean setArrayElement(final int[] array, final int i, final Object n) { + if (n instanceof Number) { + array[i] = ((Number)n).intValue(); + return true; + } + return false; + } + + private static boolean setArrayElement(final long[] array, final int i, final Object n) { + if (n instanceof Number) { + array[i] = ((Number)n).longValue(); + return true; + } + return false; + } + + private static boolean setArrayElement(final double[] array, final int i, final Object n) { + if (n instanceof Number) { + array[i] = ((Number)n).doubleValue(); + return true; + } + return false; + } + + private static int[] presetIntArray(final Expression[] value, final int[] postsets) { + final int[] array = new int[value.length]; + int nComputed = 0; + for (int i = 0; i < value.length; i++) { + if (!setArrayElement(array, i, objectAsConstant(value[i]))) { + assert postsets[nComputed++] == i; + } + } + assert postsets.length == nComputed; + return array; + } + + private static long[] presetLongArray(final Expression[] value, final int[] postsets) { + final long[] array = new long[value.length]; + int nComputed = 0; + for (int i = 0; i < value.length; i++) { + if (!setArrayElement(array, i, objectAsConstant(value[i]))) { + assert postsets[nComputed++] == i; + } + } + assert postsets.length == nComputed; + return array; + } + + private static double[] presetDoubleArray(final Expression[] value, final int[] postsets) { + final double[] array = new double[value.length]; + int nComputed = 0; + for (int i = 0; i < value.length; i++) { + if (!setArrayElement(array, i, objectAsConstant(value[i]))) { + assert postsets[nComputed++] == i; + } + } + assert postsets.length == nComputed; + return array; + } + + private static Object[] presetObjectArray(final Expression[] value, final int[] postsets) { + final Object[] array = new Object[value.length]; + int nComputed = 0; + + for (int i = 0; i < value.length; i++) { + final Node node = value[i]; + + if (node == null) { + assert postsets[nComputed++] == i; + continue; + } + final Object element = objectAsConstant(node); + + if (element != POSTSET_MARKER) { + array[i] = element; + } else { + assert postsets[nComputed++] == i; + } + } + + assert postsets.length == nComputed; + return array; + } + + static Object computePresets(final Expression[] value, final Type elementType, final int[] postsets) { + assert !elementType.isUnknown(); + if (elementType.isInteger()) { + return presetIntArray(value, postsets); + } else if (elementType.isLong()) { + return presetLongArray(value, postsets); + } else if (elementType.isNumeric()) { + return presetDoubleArray(value, postsets); + } else { + return presetObjectArray(value, postsets); + } + } + } + /** * Constructor * @@ -644,136 +802,21 @@ protected ArrayLiteralNode(final long token, final int finish, final Expression[] value) { super(Token.recast(token, TokenType.ARRAY), finish, value); this.elementType = Type.UNKNOWN; + this.presets = null; + this.postsets = null; + this.units = null; } /** * Copy constructor * @param node source array literal node */ - private ArrayLiteralNode(final ArrayLiteralNode node, final Expression[] value) { + private ArrayLiteralNode(final ArrayLiteralNode node, final Expression[] value, final Type elementType, final int[] postsets, final Object presets, final List units) { super(node, value); - this.elementType = node.elementType; - this.presets = node.presets; - this.postsets = node.postsets; - this.units = node.units; - } - - /** - * Compute things like widest element type needed. Internal use from compiler only - */ - public void analyze() { - assert elementType.isUnknown(); - elementType = getNarrowestElementType(value); - } - - private int[] presetIntArray() { - final int[] array = new int[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Object element = objectAsConstant(value[i]); - - if (element instanceof Number) { - array[i] = ((Number)element).intValue(); - } else { - assert getPostsets()[nComputed++] == i; - } - } - - assert getPostsets().length == nComputed; - return array; - } - - private long[] presetLongArray() { - final long[] array = new long[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Object element = objectAsConstant(value[i]); - - if (element instanceof Number) { - array[i] = ((Number)element).longValue(); - } else { - assert getPostsets()[nComputed++] == i; - } - } - - assert getPostsets().length == nComputed; - return array; - } - - private double[] presetNumberArray() { - final double[] array = new double[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Object element = objectAsConstant(value[i]); - - if (element instanceof Number) { - array[i] = ((Number)element).doubleValue(); - } else { - assert getPostsets()[nComputed++] == i; - } - } - - assert getPostsets().length == nComputed; - return array; - } - - private Object[] presetObjectArray() { - final Object[] array = new Object[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Node node = value[i]; - - if (node == null) { - assert getPostsets()[nComputed++] == i; - } else { - final Object element = objectAsConstant(node); - - if (element != POSTSET_MARKER) { - array[i] = element; - } else { - assert getPostsets()[nComputed++] == i; - } - } - } - - assert getPostsets().length == nComputed; - return array; - } - - /** - * Returns the narrowest element type that is wide enough to represent all the expressions in the array. - * @param elementExpressions the array of expressions - * @return the narrowest element type that is wide enough to represent all the expressions in the array. - */ - private static Type getNarrowestElementType(final Expression[] elementExpressions) { - Type widestElementType = Type.INT; - for (final Expression element : elementExpressions) { - if (element == null) { - widestElementType = widestElementType.widest(Type.OBJECT); //no way to represent undefined as number - break; - } - - Type elementType = element.getType(); - if (elementType.isUnknown()) { - elementType = Type.OBJECT; - } - - if (elementType.isBoolean()) { - widestElementType = widestElementType.widest(Type.OBJECT); - break; - } - - widestElementType = widestElementType.widest(elementType); - - if (widestElementType.isObject()) { - break; - } - } - return widestElementType; + this.elementType = elementType; + this.postsets = postsets; + this.presets = presets; + this.units = units; } @Override @@ -782,6 +825,19 @@ } /** + * Setter that initializes all code generation meta data for an + * ArrayLiteralNode. This acts a setter, so the return value may + * return a new node and must be handled + * + * @param lc lexical context + * @return new array literal node with postsets, presets and element types initialized + */ + @Override + public ArrayLiteralNode initialize(final LexicalContext lc) { + return Node.replaceInLexicalContext(lc, this, ArrayLiteralInitializer.initialize(this)); + } + + /** * Get the array element type as Java format, e.g. [I * @return array element type */ @@ -811,7 +867,7 @@ * @return element type */ public Type getElementType() { - assert !elementType.isUnknown(); + assert !elementType.isUnknown() : this + " has elementType=unknown"; return elementType; } @@ -821,19 +877,20 @@ * @return post set indices */ public int[] getPostsets() { - if(postsets == null) { - final int[] computed = new int[value.length]; - int nComputed = 0; + assert postsets != null : this + " elementType=" + elementType + " has no postsets"; + return postsets; + } - for (int i = 0; i < value.length; i++) { - final Expression element = value[i]; - if(element == null || objectAsConstant(element) == POSTSET_MARKER) { - computed[nComputed++] = i; - } - } - postsets = Arrays.copyOf(computed, nComputed); + private boolean presetsMatchElementType() { + if (elementType == Type.INT) { + return presets instanceof int[]; + } else if (elementType == Type.LONG) { + return presets instanceof long[]; + } else if (elementType == Type.NUMBER) { + return presets instanceof double[]; + } else { + return presets instanceof Object[]; } - return postsets; } /** @@ -841,18 +898,7 @@ * @return presets array, always returns an array type */ public Object getPresets() { - if(presets == null) { - final Type type = getElementType(); - if (type.isInteger()) { - presets = presetIntArray(); - } else if (type.isLong()) { - presets = presetLongArray(); - } else if (type.isNumeric()) { - presets = presetNumberArray(); - } else { - presets = presetObjectArray(); - } - } + assert presets != null && presetsMatchElementType() : this + " doesn't have presets, or invalid preset type: " + presets; return presets; } @@ -867,11 +913,16 @@ /** * Set the ArrayUnits that make up this ArrayLiteral + * @param lc lexical context * @see ArrayUnit * @param units list of array units + * @return new or changed arrayliteralnode */ - public void setUnits(final List units) { - this.units = units; + public ArrayLiteralNode setUnits(final LexicalContext lc, final List units) { + if (this.units == units) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ArrayLiteralNode(this, value, elementType, postsets, presets, units)); } @Override @@ -889,8 +940,15 @@ return this; } + private ArrayLiteralNode setValue(final LexicalContext lc, final Expression[] value) { + if (this.value == value) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ArrayLiteralNode(this, value, elementType, postsets, presets, units)); + } + private ArrayLiteralNode setValue(final LexicalContext lc, final List value) { - return (ArrayLiteralNode)lc.replace(this, new ArrayLiteralNode(this, value.toArray(new Expression[value.size()]))); + return setValue(lc, value.toArray(new Expression[value.size()])); } @Override diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/Node.java --- a/nashorn/src/jdk/nashorn/internal/ir/Node.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/Node.java Tue Jul 01 14:27:28 2014 -0700 @@ -144,15 +144,6 @@ public abstract void toString(final StringBuilder sb, final boolean printType); /** - * Check if this node has terminal flags, i.e. ends or breaks control flow - * - * @return true if terminal - */ - public boolean hasTerminalFlags() { - return isTerminal() || hasGoto(); - } - - /** * Get the finish position for this node in the source string * @return finish */ @@ -169,15 +160,6 @@ } /** - * Check if this function repositions control flow with goto like - * semantics, for example {@link BreakNode} or a {@link ForNode} with no test - * @return true if node has goto semantics - */ - public boolean hasGoto() { - return false; - } - - /** * Get start position for node * @return start position */ @@ -249,16 +231,6 @@ return token; } - /** - * Is this a terminal Node, i.e. does it end control flow like a throw or return - * expression does? - * - * @return true if this node is terminal - */ - public boolean isTerminal() { - return false; - } - //on change, we have to replace the entire list, that's we can't simple do ListIterator.set static List accept(final NodeVisitor visitor, final Class clazz, final List list) { boolean changed = false; diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/Statement.java --- a/nashorn/src/jdk/nashorn/internal/ir/Statement.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/Statement.java Tue Jul 01 14:27:28 2014 -0700 @@ -30,7 +30,7 @@ * made up of statements. The only node subclass that needs to keep token and * location information is the Statement */ -public abstract class Statement extends Node { +public abstract class Statement extends Node implements Terminal { private final int lineNumber; @@ -77,4 +77,32 @@ return lineNumber; } + /** + * Is this a terminal statement, i.e. does it end control flow like a throw or return? + * + * @return true if this node statement is terminal + */ + @Override + public boolean isTerminal() { + return false; + } + + /** + * Check if this statement repositions control flow with goto like + * semantics, for example {@link BreakNode} or a {@link ForNode} with no test + * @return true if statement has goto semantics + */ + public boolean hasGoto() { + return false; + } + + /** + * Check if this statement has terminal flags, i.e. ends or breaks control flow + * + * @return true if has terminal flags + */ + public final boolean hasTerminalFlags() { + return isTerminal() || hasGoto(); + } } + diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/Terminal.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/src/jdk/nashorn/internal/ir/Terminal.java Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010, 2014, 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 for AST nodes that can have a flag determining if they can terminate function control flow. + */ +public interface Terminal { + /** + * Returns true if this AST node is (or contains) a statement that terminates function control flow. + * @return true if this AST node is (or contains) a statement that terminates function control flow. + */ + public boolean isTerminal(); +} diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java --- a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java Tue Jul 01 14:27:28 2014 -0700 @@ -38,7 +38,9 @@ import jdk.nashorn.internal.ir.Expression; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.ir.Terminal; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.annotations.Ignore; import jdk.nashorn.internal.ir.annotations.Reference; @@ -144,11 +146,11 @@ String status = ""; - if (node.isTerminal()) { + if (node instanceof Terminal && ((Terminal)node).isTerminal()) { status += " Terminal"; } - if (node.hasGoto()) { + if (node instanceof Statement && ((Statement)node).hasGoto()) { status += " Goto "; } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/debug/NashornClassReader.java --- a/nashorn/src/jdk/nashorn/internal/ir/debug/NashornClassReader.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/debug/NashornClassReader.java Tue Jul 01 14:27:28 2014 -0700 @@ -443,7 +443,7 @@ @Override protected Label readLabel(final int offset, final Label[] labels) { final Label label = super.readLabel(offset, labels); - label.info = (int)offset; + label.info = offset; return label; } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java --- a/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java Tue Jul 01 14:27:28 2014 -0700 @@ -182,9 +182,9 @@ final List statements = block.getStatements(); - for (final Node statement : statements) { - if (printLineNumbers && (statement instanceof Statement)) { - final int lineNumber = ((Statement)statement).getLineNumber(); + for (final Statement statement : statements) { + if (printLineNumbers) { + final int lineNumber = statement.getLineNumber(); sb.append('\n'); if (lineNumber != lastLineNumber) { indent(); @@ -196,10 +196,6 @@ statement.accept(this); - if (statement instanceof FunctionNode) { - continue; - } - int lastIndex = sb.length() - 1; char lastChar = sb.charAt(lastIndex); while (Character.isWhitespace(lastChar) && lastIndex >= 0) { diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/objects/Global.java --- a/nashorn/src/jdk/nashorn/internal/objects/Global.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java Tue Jul 01 14:27:28 2014 -0700 @@ -883,7 +883,7 @@ final Global global = Global.instance(); final ScriptObject scope = self instanceof ScriptObject ? (ScriptObject)self : global; - return global.getContext().eval(scope, str.toString(), callThis, location, Boolean.TRUE.equals(strict)); + return global.getContext().eval(scope, str.toString(), callThis, location, Boolean.TRUE.equals(strict), true); } /** diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/objects/NativeDate.java --- a/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java Tue Jul 01 14:27:28 2014 -0700 @@ -909,6 +909,7 @@ sb.append(n); } + @SuppressWarnings("fallthrough") private static String toStringImpl(final Object self, final int format) { final NativeDate nd = getNativeDate(self); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/objects/NativeObject.java --- a/nashorn/src/jdk/nashorn/internal/objects/NativeObject.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/objects/NativeObject.java Tue Jul 01 14:27:28 2014 -0700 @@ -716,6 +716,32 @@ return target; } + /* + * Binds the source mirror object's properties to the target object. Binding + * properties allows two-way read/write for the properties of the source object. + * All inherited, enumerable properties are also bound. This method is used to + * to make 'with' statement work with ScriptObjectMirror as scope object. + * + * @param target the target object to which the source object's properties are bound + * @param source the source object whose properties are bound to the target + * @return the target object after property binding + */ + public static Object bindAllProperties(final ScriptObject target, final ScriptObjectMirror source) { + final Set keys = source.keySet(); + // make accessor properties using dynamic invoker getters and setters + final AccessorProperty[] props = new AccessorProperty[keys.size()]; + int idx = 0; + for (String name : keys) { + final MethodHandle getter = Bootstrap.createDynamicInvoker("dyn:getMethod|getProp|getElem:" + name, MIRROR_GETTER_TYPE); + final MethodHandle setter = Bootstrap.createDynamicInvoker("dyn:setProp|setElem:" + name, MIRROR_SETTER_TYPE); + props[idx] = AccessorProperty.create(name, 0, getter, setter); + idx++; + } + + target.addBoundProperties(source, props); + return target; + } + private static void bindBeanProperties(final ScriptObject targetObj, final Object source, final Collection readablePropertyNames, final Collection writablePropertyNames, final Collection methodNames) { diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java --- a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java Tue Jul 01 14:27:28 2014 -0700 @@ -89,11 +89,12 @@ this.log = log; } - CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData, final int flags) { + CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData, + final Map invalidatedProgramPoints, final int flags) { this(invoker, null, functionData.getLogger()); this.flags = flags; if ((flags & FunctionNode.IS_DEOPTIMIZABLE) != 0) { - optimismInfo = new OptimismInfo(functionData); + optimismInfo = new OptimismInfo(functionData, invalidatedProgramPoints); } else { optimismInfo = null; } @@ -691,13 +692,14 @@ private static class OptimismInfo { // TODO: this is pointing to its owning ScriptFunctionData. Re-evaluate if that's okay. private final RecompilableScriptFunctionData data; - private final Map invalidatedProgramPoints = new TreeMap<>(); + private final Map invalidatedProgramPoints; private SwitchPoint optimisticAssumptions; private final DebugLogger log; - OptimismInfo(final RecompilableScriptFunctionData data) { + OptimismInfo(final RecompilableScriptFunctionData data, final Map invalidatedProgramPoints) { this.data = data; this.log = data.getLogger(); + this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new TreeMap() : invalidatedProgramPoints; newOptimisticAssumptions(); } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/ConsString.java --- a/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java Tue Jul 01 14:27:28 2014 -0700 @@ -36,8 +36,12 @@ public final class ConsString implements CharSequence { private CharSequence left, right; - final private int length; - private boolean flat = false; + private final int length; + private volatile int state = STATE_NEW; + + private final static int STATE_NEW = 0; + private final static int STATE_THRESHOLD = 2; + private final static int STATE_FLATTENED = -1; /** * Constructor @@ -53,11 +57,14 @@ this.left = left; this.right = right; length = left.length() + right.length(); + if (length < 0) { + throw new IllegalArgumentException("too big concatenated String"); + } } @Override public String toString() { - return (String) flattened(); + return (String) flattened(true); } @Override @@ -67,22 +74,31 @@ @Override public char charAt(final int index) { - return flattened().charAt(index); + return flattened(true).charAt(index); } @Override public CharSequence subSequence(final int start, final int end) { - return flattened().subSequence(start, end); + return flattened(true).subSequence(start, end); } - private CharSequence flattened() { - if (!flat) { - flatten(); + /** + * Returns the components of this ConsString as a {@code CharSequence} array with two elements. + * The elements will be either {@code Strings} or other {@code ConsStrings}. + * @return CharSequence array of length 2 + */ + public synchronized CharSequence[] getComponents() { + return new CharSequence[] { left, right }; + } + + private CharSequence flattened(boolean flattenNested) { + if (state != STATE_FLATTENED) { + flatten(flattenNested); } return left; } - private void flatten() { + private synchronized void flatten(boolean flattenNested) { // We use iterative traversal as recursion may exceed the stack size limit. final char[] chars = new char[length]; int pos = length; @@ -97,8 +113,14 @@ do { if (cs instanceof ConsString) { final ConsString cons = (ConsString) cs; - stack.addFirst(cons.left); - cs = cons.right; + // Count the times a cons-string is traversed as part of other cons-strings being flattened. + // If it crosses a threshold we flatten the nested cons-string internally. + if (cons.state == STATE_FLATTENED || (flattenNested && ++cons.state >= STATE_THRESHOLD)) { + cs = cons.flattened(false); + } else { + stack.addFirst(cons.left); + cs = cons.right; + } } else { final String str = (String) cs; pos -= str.length(); @@ -109,7 +131,7 @@ left = new String(chars); right = ""; - flat = true; + state = STATE_FLATTENED; } } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/Context.java --- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java Tue Jul 01 14:27:28 2014 -0700 @@ -444,15 +444,11 @@ } if (env._persistent_cache) { - if (env._lazy_compilation || env._optimistic_types) { - getErr().println("Can not use persistent class caching with lazy compilation or optimistic compilation."); - } else { - try { - final String cacheDir = Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"); - codeStore = new CodeStore(cacheDir); - } catch (final IOException e) { - throw new RuntimeException("Error initializing code cache", e); - } + try { + final String cacheDir = Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"); + codeStore = new CodeStore(cacheDir); + } catch (final IOException e) { + throw new RuntimeException("Error initializing code cache", e); } } @@ -560,12 +556,29 @@ * @param callThis "this" to be passed to the evaluated code * @param location location of the eval call * @param strict is this {@code eval} call from a strict mode code? + * @return the return value of the {@code eval} + */ + public Object eval(final ScriptObject initialScope, final String string, + final Object callThis, final Object location, final boolean strict) { + return eval(initialScope, string, callThis, location, strict, false); + } + + /** + * Entry point for {@code eval} + * + * @param initialScope The scope of this eval call + * @param string Evaluated code as a String + * @param callThis "this" to be passed to the evaluated code + * @param location location of the eval call + * @param strict is this {@code eval} call from a strict mode code? + * @param evalCall is this called from "eval" builtin? * * @return the return value of the {@code eval} */ - public Object eval(final ScriptObject initialScope, final String string, final Object callThis, final Object location, final boolean strict) { + public Object eval(final ScriptObject initialScope, final String string, + final Object callThis, final Object location, final boolean strict, final boolean evalCall) { final String file = location == UNDEFINED || location == null ? "" : location.toString(); - final Source source = sourceFor(file, string); + final Source source = sourceFor(file, string, evalCall); final boolean directEval = location != UNDEFINED; // is this direct 'eval' call or indirectly invoked eval? final Global global = Context.getGlobal(); ScriptObject scope = initialScope; @@ -1162,7 +1175,7 @@ for (final Object constant : constants) { if (constant instanceof RecompilableScriptFunctionData) { - ((RecompilableScriptFunctionData) constant).setCodeAndSource(installedClasses, source); + ((RecompilableScriptFunctionData) constant).initTransients(source, installer); } } diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/JSType.java --- a/nashorn/src/jdk/nashorn/internal/runtime/JSType.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/JSType.java Tue Jul 01 14:27:28 2014 -0700 @@ -130,6 +130,9 @@ /** Combined call to toPrimitive followed by toString. */ public static final Call TO_PRIMITIVE_TO_STRING = staticCall(JSTYPE_LOOKUP, JSType.class, "toPrimitiveToString", String.class, Object.class); + /** Combined call to toPrimitive followed by toCharSequence. */ + public static final Call TO_PRIMITIVE_TO_CHARSEQUENCE = staticCall(JSTYPE_LOOKUP, JSType.class, "toPrimitiveToCharSequence", CharSequence.class, Object.class); + /** Throw an unwarranted optimism exception */ public static final Call THROW_UNWARRANTED = staticCall(JSTYPE_LOOKUP, JSType.class, "throwUnwarrantedOptimismException", Object.class, Object.class, int.class); @@ -488,6 +491,16 @@ } /** + * Like {@link #toPrimitiveToString(Object)}, but avoids conversion of ConsString to String. + * + * @param obj an object + * @return the CharSequence form of the primitive form of the object + */ + public static CharSequence toPrimitiveToCharSequence(final Object obj) { + return toCharSequence(toPrimitive(obj)); + } + + /** * JavaScript compliant conversion of number to boolean * * @param num a number diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java --- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Tue Jul 01 14:27:28 2014 -0700 @@ -28,7 +28,6 @@ import static jdk.nashorn.internal.lookup.Lookup.MH; import java.io.IOException; -import java.io.Serializable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -36,12 +35,14 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import jdk.internal.dynalink.support.NameCodec; import jdk.nashorn.internal.codegen.CompileUnit; import jdk.nashorn.internal.codegen.Compiler; import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.FunctionSignature; +import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; import jdk.nashorn.internal.codegen.TypeMap; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; @@ -54,7 +55,6 @@ import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; import jdk.nashorn.internal.runtime.logging.Logger; -import jdk.nashorn.internal.scripts.JS; /** * This is a subclass that represents a script function that may be regenerated, @@ -83,9 +83,6 @@ /** Source from which FunctionNode was parsed. */ private transient Source source; - /** Allows us to retrieve the method handle for this function once the code is compiled */ - private MethodLocator methodLocator; - /** Token of this function within the source. */ private final long token; @@ -237,15 +234,18 @@ } /** - * Setter for code and source + * Initialize transient fields on deserialized instances * - * @param code map of code, class name to class * @param source source + * @param installer code installer */ - public void setCodeAndSource(final Map> code, final Source source) { - this.source = source; - if (methodLocator != null) { - methodLocator.setClass(code.get(methodLocator.getClassName())); + public void initTransients(final Source source, final CodeInstaller installer) { + if (this.source == null && this.installer == null) { + this.source = source; + this.installer = installer; + } else if (this.source != source || this.installer != installer) { + // Existing values must be same as those passed as parameters + throw new IllegalArgumentException(); } } @@ -390,7 +390,11 @@ return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null); } - Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType, final ScriptObject runtimeScope, final Map ipp, final int[] cep) { + Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType, + final ScriptObject runtimeScope, final Map invalidatedProgramPoints, + final int[] continuationEntryPoints) { + final TypeMap typeMap = typeMap(actualCallSiteType); + final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, typeMap == null ? null : typeMap.getParameterTypes(functionNodeId)); final Context context = Context.getContextTrusted(); return new Compiler( context, @@ -402,12 +406,31 @@ true, // is on demand this, // compiledFunction, i.e. this RecompilableScriptFunctionData typeMap(actualCallSiteType), // type map - ipp, // invalidated program points - cep, // continuation entry points + getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points + typeInformationFile, + continuationEntryPoints, // continuation entry points runtimeScope); // runtime scope } - private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) { + /** + * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to + * load invalidated program points map from the persistent type info cache. + * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function + * doesn't have it. + * @param typeInformationFile the object describing the location of the persisted type information. + * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if + * neither an existing map or a persistent cached type info is available. + */ + private static Map getEffectiveInvalidatedProgramPoints( + final Map invalidatedProgramPoints, final Object typeInformationFile) { + if(invalidatedProgramPoints != null) { + return invalidatedProgramPoints; + } + final Map loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile); + return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap(); + } + + private TypeSpecializedFunction compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) { // We're creating an empty script object for holding local variables. AssignSymbols will populate it with // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and // CompilationEnvironment#declareLocalSymbol()). @@ -417,7 +440,20 @@ } final FunctionNode fn = reparse(); - return getCompiler(fn, actualCallSiteType, runtimeScope).compile(fn, CompilationPhases.COMPILE_ALL); + final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope); + + final FunctionNode compiledFn = compiler.compile(fn, CompilationPhases.COMPILE_ALL); + return new TypeSpecializedFunction(compiledFn, compiler.getInvalidatedProgramPoints()); + } + + private static class TypeSpecializedFunction { + private final FunctionNode fn; + private final Map invalidatedProgramPoints; + + TypeSpecializedFunction(final FunctionNode fn, final Map invalidatedProgramPoints) { + this.fn = fn; + this.invalidatedProgramPoints = invalidatedProgramPoints; + } } private MethodType explicitParams(final MethodType callSiteType) { @@ -491,17 +527,16 @@ throw new IllegalStateException(functionNode.getName() + " id=" + functionNode.getId()); } addCode(functionNode); - methodLocator = new MethodLocator(functionNode); } - private CompiledFunction addCode(final MethodHandle target, final int fnFlags) { - final CompiledFunction cfn = new CompiledFunction(target, this, fnFlags); + private CompiledFunction addCode(final MethodHandle target, final Map invalidatedProgramPoints, final int fnFlags) { + final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, fnFlags); code.add(cfn); return cfn; } private CompiledFunction addCode(final FunctionNode fn) { - return addCode(lookup(fn), fn.getFlags()); + return addCode(lookup(fn), null, fn.getFlags()); } /** @@ -510,11 +545,12 @@ * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups * for the same specialization, so we must adapt the handle to the expected type. - * @param fn the function + * @param tfn the function * @param callSiteType the call site type * @return the compiled function object, with its type matching that of the call site type. */ - private CompiledFunction addCode(final FunctionNode fn, final MethodType callSiteType) { + private CompiledFunction addCode(final TypeSpecializedFunction tfn, final MethodType callSiteType) { + final FunctionNode fn = tfn.fn; if (fn.isVarArg()) { return addCode(fn); } @@ -544,7 +580,7 @@ toType = toType.dropParameterTypes(fromCount, toCount); } - return addCode(lookup(fn).asType(toType), fn.getFlags()); + return addCode(lookup(fn).asType(toType), tfn.invalidatedProgramPoints, fn.getFlags()); } @@ -553,12 +589,7 @@ synchronized (code) { CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope); if (existingBest == null) { - if(code.isEmpty() && methodLocator != null) { - // This is a deserialized object, reconnect from method handle - existingBest = addCode(methodLocator.getMethodHandle(), methodLocator.getFunctionFlags()); - } else { - existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType); - } + existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType); } assert existingBest != null; @@ -576,9 +607,9 @@ } if (applyToCall) { - final FunctionNode fn = compileTypeSpecialization(callSiteType, runtimeScope); - if (fn.hasOptimisticApplyToCall()) { //did the specialization work - existingBest = addCode(fn, callSiteType); + final TypeSpecializedFunction tfn = compileTypeSpecialization(callSiteType, runtimeScope); + if (tfn.fn.hasOptimisticApplyToCall()) { //did the specialization work + existingBest = addCode(tfn, callSiteType); } } @@ -670,48 +701,6 @@ return true; } - /** - * Helper class that allows us to retrieve the method handle for this function once it has been generated. - */ - private static class MethodLocator implements Serializable { - private transient Class clazz; - private final String className; - private final String methodName; - private final MethodType methodType; - private final int functionFlags; - - private static final long serialVersionUID = -5420835725902966692L; - - MethodLocator(final FunctionNode functionNode) { - this.className = functionNode.getCompileUnit().getUnitClassName(); - this.methodName = functionNode.getName(); - this.methodType = new FunctionSignature(functionNode).getMethodType(); - this.functionFlags = functionNode.getFlags(); - - assert className != null; - assert methodName != null; - } - - void setClass(final Class clazz) { - if (!JS.class.isAssignableFrom(clazz)) { - throw new IllegalArgumentException(); - } - this.clazz = clazz; - } - - String getClassName() { - return className; - } - - MethodHandle getMethodHandle() { - return MH.findStatic(LOOKUP, clazz, methodName, methodType); - } - - int getFunctionFlags() { - return functionFlags; - } - } - private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); createLogger(); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java Tue Jul 01 14:27:28 2014 -0700 @@ -35,6 +35,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.SwitchPoint; +import java.util.Collections; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; @@ -593,6 +594,12 @@ private GuardedInvocation createApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc, final LinkRequest request, final Object[] args) { final MethodType descType = desc.getMethodType(); final int paramCount = descType.parameterCount(); + if(descType.parameterType(paramCount - 1).isArray()) { + // This is vararg invocation of apply or call. This can normally only happen when we do a recursive + // invocation of createApplyOrCallCall (because we're doing apply-of-apply). In this case, create delegate + // linkage by unpacking the vararg invocation and use pairArguments to introduce the necessary spreader. + return createVarArgApplyOrCallCall(isApply, desc, request, args); + } final boolean passesThis = paramCount > 2; final boolean passesArgs = paramCount > 3; @@ -647,6 +654,7 @@ System.arraycopy(args, 3, tmp, 0, tmp.length); appliedArgs[2] = NativeFunction.toApplyArgs(tmp); } else { + assert !isApply; System.arraycopy(args, 3, appliedArgs, 2, args.length - 3); } } else if (isFailedApplyToCall) { @@ -705,8 +713,7 @@ } final MethodType guardType = guard.type(); - // Original function guard will expect the invoked function in parameter position 0, but we're passing it in - // position 1. + // We need to account for the dropped (apply|call) function argument. guard = MH.dropArguments(guard, 0, descType.parameterType(0)); // Take the "isApplyFunction" guard, and bind it to this function. MethodHandle applyFnGuard = MH.insertArguments(IS_APPLY_FUNCTION, 2, this); @@ -718,7 +725,72 @@ return appliedInvocation.replaceMethods(inv, guard); } - private static MethodHandle bindImplicitThis(final Object fn, final MethodHandle mh) { + /* + * This method is used for linking nested apply. Specialized apply and call linking will create a variable arity + * call site for an apply call; when createApplyOrCallCall sees a linking request for apply or call with + * Nashorn-style variable arity call site (last argument type is Object[]) it'll delegate to this method. + * This method converts the link request from a vararg to a non-vararg one (unpacks the array), then delegates back + * to createApplyOrCallCall (with which it is thus mutually recursive), and adds appropriate argument spreaders to + * invocation and the guard of whatever createApplyOrCallCall returned to adapt it back into a variable arity + * invocation. It basically reduces the problem of vararg call site linking of apply and call back to the (already + * solved by createApplyOrCallCall) non-vararg call site linking. + */ + private GuardedInvocation createVarArgApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc, + final LinkRequest request, final Object[] args) { + final MethodType descType = desc.getMethodType(); + final int paramCount = descType.parameterCount(); + final Object[] varArgs = (Object[])args[paramCount - 1]; + // -1 'cause we're not passing the vararg array itself + final int copiedArgCount = args.length - 1; + int varArgCount = varArgs.length; + + // Spread arguments for the delegate createApplyOrCallCall invocation. + final Object[] spreadArgs = new Object[copiedArgCount + varArgCount]; + System.arraycopy(args, 0, spreadArgs, 0, copiedArgCount); + System.arraycopy(varArgs, 0, spreadArgs, copiedArgCount, varArgCount); + + // Spread call site descriptor for the delegate createApplyOrCallCall invocation. We drop vararg array and + // replace it with a list of Object.class. + final MethodType spreadType = descType.dropParameterTypes(paramCount - 1, paramCount).appendParameterTypes( + Collections.>nCopies(varArgCount, Object.class)); + final CallSiteDescriptor spreadDesc = desc.changeMethodType(spreadType); + + // Delegate back to createApplyOrCallCall with the spread (that is, reverted to non-vararg) request/ + final LinkRequest spreadRequest = request.replaceArguments(spreadDesc, spreadArgs); + final GuardedInvocation spreadInvocation = createApplyOrCallCall(isApply, spreadDesc, spreadRequest, spreadArgs); + + // Add spreader combinators to returned invocation and guard. + return spreadInvocation.replaceMethods( + // Use standard ScriptObject.pairArguments on the invocation + pairArguments(spreadInvocation.getInvocation(), descType), + // Use our specialized spreadGuardArguments on the guard (see below). + spreadGuardArguments(spreadInvocation.getGuard(), descType)); + } + + private static MethodHandle spreadGuardArguments(final MethodHandle guard, final MethodType descType) { + final MethodType guardType = guard.type(); + final int guardParamCount = guardType.parameterCount(); + final int descParamCount = descType.parameterCount(); + final int spreadCount = guardParamCount - descParamCount + 1; + if (spreadCount <= 0) { + // Guard doesn't dip into the varargs + return guard; + } + + final MethodHandle arrayConvertingGuard; + // If the last parameter type of the guard is an array, then it is already itself a guard for a vararg apply + // invocation. We must filter the last argument with toApplyArgs otherwise deeper levels of nesting will fail + // with ClassCastException of NativeArray to Object[]. + if(guardType.parameterType(guardParamCount - 1).isArray()) { + arrayConvertingGuard = MH.filterArguments(guard, guardParamCount - 1, NativeFunction.TO_APPLY_ARGS); + } else { + arrayConvertingGuard = guard; + } + + return ScriptObject.adaptHandleToVarArgCallSite(arrayConvertingGuard, descParamCount); + } + + private static MethodHandle bindImplicitThis(final Object fn, final MethodHandle mh) { final MethodHandle bound; if(fn instanceof ScriptFunction && ((ScriptFunction)fn).needsWrappedThis()) { bound = MH.filterArguments(mh, 1, SCRIPTFUNCTION_GLOBALFILTER); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java Tue Jul 01 14:27:28 2014 -0700 @@ -691,6 +691,7 @@ assert isValidArrayIndex(index) : "invalid array index"; final long longIndex = ArrayIndex.toLongIndex(index); doesNotHaveEnsureDelete(longIndex, getArray().length(), false); + setArray(getArray().ensure(longIndex)); setArray(getArray().set(index, value, false)); } @@ -2499,18 +2500,7 @@ } if (isCallerVarArg) { - final int spreadArgs = parameterCount - callCount + 1; - return MH.filterArguments( - MH.asSpreader( - methodHandle, - Object[].class, - spreadArgs), - callCount - 1, - MH.insertArguments( - TRUNCATINGFILTER, - 0, - spreadArgs) - ); + return adaptHandleToVarArgCallSite(methodHandle, callCount); } if (callCount < parameterCount) { @@ -2541,6 +2531,21 @@ return methodHandle; } + static MethodHandle adaptHandleToVarArgCallSite(final MethodHandle mh, final int callSiteParamCount) { + final int spreadArgs = mh.type().parameterCount() - callSiteParamCount + 1; + return MH.filterArguments( + MH.asSpreader( + mh, + Object[].class, + spreadArgs), + callSiteParamCount - 1, + MH.insertArguments( + TRUNCATINGFILTER, + 0, + spreadArgs) + ); + } + @SuppressWarnings("unused") private static Object[] truncatingFilter(final int n, final Object[] array) { final int length = array == null ? 0 : array.length; diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java Tue Jul 01 14:27:28 2014 -0700 @@ -27,6 +27,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; +import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; @@ -49,6 +50,7 @@ import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.ir.debug.JSONWriter; import jdk.nashorn.internal.objects.Global; +import jdk.nashorn.internal.objects.NativeObject; import jdk.nashorn.internal.parser.Lexer; import jdk.nashorn.internal.runtime.linker.Bootstrap; @@ -478,9 +480,21 @@ throw typeError(global, "cant.apply.with.to.null"); } - final Object wrappedExpr = JSType.toScriptObject(global, expression); - if (wrappedExpr instanceof ScriptObject) { - return new WithObject(scope, (ScriptObject)wrappedExpr); + if (expression instanceof ScriptObjectMirror) { + final Object unwrapped = ScriptObjectMirror.unwrap(expression, global); + if (unwrapped instanceof ScriptObject) { + return new WithObject(scope, (ScriptObject)unwrapped); + } else { + // foreign ScriptObjectMirror + ScriptObject exprObj = global.newObject(); + NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression); + return new WithObject(scope, exprObj); + } + } else { + final Object wrappedExpr = JSType.toScriptObject(global, expression); + if (wrappedExpr instanceof ScriptObject) { + return new WithObject(scope, (ScriptObject)wrappedExpr); + } } throw typeError(global, "cant.apply.with.to.non.scriptobject"); @@ -518,7 +532,11 @@ if (xPrim instanceof String || yPrim instanceof String || xPrim instanceof ConsString || yPrim instanceof ConsString) { - return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); + try { + return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); + } catch (final IllegalArgumentException iae) { + throw rangeError(iae, "concat.string.too.big"); + } } return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java Tue Jul 01 14:27:28 2014 -0700 @@ -107,8 +107,8 @@ if (file instanceof File) { f = (File)file; - } else if (file instanceof String) { - f = new java.io.File((String)file); + } else if (file instanceof String || file instanceof ConsString) { + f = new java.io.File(((CharSequence)file).toString()); } if (f == null || !f.isFile()) { diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/Source.java --- a/nashorn/src/jdk/nashorn/internal/runtime/Source.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/Source.java Tue Jul 01 14:27:28 2014 -0700 @@ -142,29 +142,34 @@ long lastModified(); char[] array(); + + boolean isEvalCode(); } private static class RawData implements Data { private final char[] array; + private final boolean evalCode; private int hash; - private RawData(final char[] array) { + private RawData(final char[] array, final boolean evalCode) { this.array = Objects.requireNonNull(array); + this.evalCode = evalCode; } - private RawData(final String source) { + private RawData(final String source, final boolean evalCode) { this.array = Objects.requireNonNull(source).toCharArray(); + this.evalCode = evalCode; } private RawData(final Reader reader) throws IOException { - this(readFully(reader)); + this(readFully(reader), false); } @Override public int hashCode() { int h = hash; if (h == 0) { - h = hash = Arrays.hashCode(array); + h = hash = Arrays.hashCode(array) ^ (evalCode? 1 : 0); } return h; } @@ -175,7 +180,8 @@ return true; } if (obj instanceof RawData) { - return Arrays.equals(array, ((RawData)obj).array); + final RawData other = (RawData)obj; + return Arrays.equals(array, other.array) && evalCode == other.evalCode; } return false; } @@ -206,6 +212,10 @@ } + @Override + public boolean isEvalCode() { + return evalCode; + } } private static class URLData implements Data { @@ -287,10 +297,16 @@ return array; } + @Override + public boolean isEvalCode() { + return false; + } + boolean isDeferred() { return array == null; } + @SuppressWarnings("try") protected void checkPermissionAndClose() throws IOException { try (InputStream in = url.openStream()) { // empty @@ -373,11 +389,23 @@ * * @param name source name * @param content contents as char array + * @param isEval does this represent code from 'eval' call? + * @return source instance + */ + public static Source sourceFor(final String name, final char[] content, final boolean isEval) { + return new Source(name, baseName(name), new RawData(content, isEval)); + } + + /** + * Returns a Source instance + * + * @param name source name + * @param content contents as char array * * @return source instance */ public static Source sourceFor(final String name, final char[] content) { - return new Source(name, baseName(name), new RawData(content)); + return sourceFor(name, content, false); } /** @@ -385,11 +413,22 @@ * * @param name source name * @param content contents as string + * @param isEval does this represent code from 'eval' call? + * @return source instance + */ + public static Source sourceFor(final String name, final String content, final boolean isEval) { + return new Source(name, baseName(name), new RawData(content, isEval)); + } + + /** + * Returns a Source instance * + * @param name source name + * @param content contents as string * @return source instance */ public static Source sourceFor(final String name, final String content) { - return new Source(name, baseName(name), new RawData(content)); + return sourceFor(name, content, false); } /** @@ -555,6 +594,15 @@ } /** + * Returns whether this source was submitted via 'eval' call or not. + * + * @return true if this source represents code submitted via 'eval' + */ + public boolean isEvalCode() { + return data.isEvalCode(); + } + + /** * Find the beginning of the line containing position. * @param position Index to offending token. * @return Index of first character of line. diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java --- a/nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java Tue Jul 01 14:27:28 2014 -0700 @@ -201,7 +201,7 @@ @Override public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { - return userAccessorGetter(getAccessors((owner != null) ? owner : (ScriptObject)self), self); + return userAccessorGetter(getAccessors((owner != null) ? owner : self), self); } @Override @@ -221,7 +221,7 @@ @Override public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { - userAccessorSetter(getAccessors((owner != null) ? owner : (ScriptObject)self), strict ? getKey() : null, self, value); + userAccessorSetter(getAccessors((owner != null) ? owner : self), strict ? getKey() : null, self, value); } @Override diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/WithObject.java --- a/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java Tue Jul 01 14:27:28 2014 -0700 @@ -31,6 +31,8 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.SwitchPoint; +import jdk.nashorn.api.scripting.AbstractJSObject; +import jdk.nashorn.api.scripting.ScriptObjectMirror; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; @@ -312,7 +314,22 @@ @SuppressWarnings("unused") private static Object bindToExpression(final Object fn, final Object receiver) { - return fn instanceof ScriptFunction ? bindToExpression((ScriptFunction) fn, receiver) : fn; + if (fn instanceof ScriptFunction) { + return bindToExpression((ScriptFunction) fn, receiver); + } else if (fn instanceof ScriptObjectMirror) { + final ScriptObjectMirror mirror = (ScriptObjectMirror)fn; + if (mirror.isFunction()) { + // We need to make sure correct 'this' is used for calls with Ident call + // expressions. We do so here using an AbstractJSObject instance. + return new AbstractJSObject() { + public Object call(final Object thiz, final Object... args) { + return mirror.call(withFilterExpression(receiver), args); + } + }; + } + } + + return fn; } private static Object bindToExpression(final ScriptFunction fn, final Object receiver) { diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/arrays/ByteBufferArrayData.java --- a/nashorn/src/jdk/nashorn/internal/runtime/arrays/ByteBufferArrayData.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/arrays/ByteBufferArrayData.java Tue Jul 01 14:27:28 2014 -0700 @@ -150,7 +150,7 @@ @Override public Object getObject(final int index) { - return (int)(0x0ff & buf.get(index)); + return 0x0ff & buf.get(index); } @Override diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java --- a/nashorn/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java Tue Jul 01 14:27:28 2014 -0700 @@ -90,7 +90,7 @@ if(plength == 0) { finalPtypes = new Class[] { Object.class, targetClass }; } else { - finalPtypes = new Class[plength + 2]; + finalPtypes = new Class[plength + 2]; finalPtypes[0] = Object.class; finalPtypes[1] = targetClass; System.arraycopy(ptypes, 0, finalPtypes, 2, plength); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties --- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties Tue Jul 01 14:27:28 2014 -0700 @@ -151,6 +151,7 @@ range.error.invalid.radix=radix argument must be in [2, 36] range.error.invalid.date=Invalid Date range.error.too.many.errors=Script contains too many errors: {0} errors +range.error.concat.string.too.big=Concatenated String is too big reference.error.not.defined="{0}" is not defined reference.error.cant.be.used.as.lhs="{0}" can not be used as the left-hand side of assignment diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED --- a/nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED Tue Jul 01 14:27:28 2014 -0700 @@ -1,3 +1,3 @@ ReferenceError: "g" is not defined - at (test/script/basic/JDK-8030182_2.js#42:4@0:-1) + at (test/script/basic/JDK-8030182_2.js#42:4@1:-1) at (test/script/basic/JDK-8030182_2.js:42) diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8046013.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8046013.js Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014, 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-8046013: TypeError: Cannot apply "with" to non script object + * + * @test + * @run + */ + +var obj = loadWithNewGlobal({ + script: "({ f: 33 })", + name: "test" +}); + +with (obj) { + print("f = " + f); +} + +var obj2 = loadWithNewGlobal({ + script: "var obj = Object.create({ foo: 42 }); obj.bar = 'hello'; obj", + name: "test2" +}); + +with (obj2) { + print("foo = " + foo); + print("bar = " + bar); +} + +var obj3 = loadWithNewGlobal({ + script: "({ f: 33, func: function() { print('this.f =', this.f); } })", + name: "test" +}); + +with(obj3) { + func(); +} diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8046013.js.EXPECTED --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8046013.js.EXPECTED Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,4 @@ +f = 33 +foo = 42 +bar = hello +this.f = 33 diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8046905.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8046905.js Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010, 2014, 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-8046905: apply on apply is broken + * + * @test + * @run + */ + +var apply = Function.prototype.apply; +var call = Function.prototype.call; +var sort = Array.prototype.sort; +var join = Array.prototype.join; + +// Running three times so that we test an already linked call site too: +// i==0: linking initially with assumed optimistic returned type int. +// i==1: linking after deoptimization with returned type Object. +// i==2: re-running code linked in previous iteration. This will +// properly exercise the guards too. +print("1 level of apply") +for(i = 0; i < 3; ++i) { + print(sort.apply([4,3,2,1])) +} +print("2 levels of apply") +for(i = 0; i < 3; ++i) { + print(apply.apply(sort,[[4,3,2,1]])) +} +print("3 levels of apply") +for(i = 0; i < 3; ++i) { + print(apply.apply(apply,[sort,[[4,3,2,1]]])) +} +print("4 levels of apply") +for(i = 0; i < 3; ++i) { + print(apply.apply(apply,[apply,[sort,[[4,3,2,1]]]])) +} +print("5 levels of apply") +for(i = 0; i < 3; ++i) { + print(apply.apply(apply,[apply,[apply,[sort,[[4,3,2,1]]]]])) +} +print("Many levels of apply!") +for(i = 0; i < 3; ++i) { + print(apply.apply(apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[sort,[[4,3,2,1]]]]]]]]]]]]]]]]]]]]]])) +} + +print("different invocations that'll trigger relinking") +var invocation = [sort,[[4,3,2,1]]]; +for(i = 0; i < 4; ++i) { + print(apply.apply(apply,[apply,invocation])) + // First change after i==1, so it relinks an otherwise stable linkage + if(i == 1) { + invocation = [sort,[[8,7,6,5]]]; + } else if(i == 2) { + invocation = [join,[[8,7,6,5],["-"]]]; + } +} + +print("Many levels of call!") +for(i = 0; i < 3; ++i) { + print(call.call(call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,sort,[4,3,2,1])) +} + +print("call apply call apply call... a lot"); +for(i = 0; i < 3; ++i) { + print(apply.call(call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [sort, [4,3,2,1]]]]]]])) +} + +print("apply call apply call apply... a lot"); +for(i = 0; i < 3; ++i) { + print(call.apply(apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, sort, [[4,3,2,1]]]]]]]]])) +} diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8046905.js.EXPECTED --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8046905.js.EXPECTED Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,41 @@ +1 level of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +2 levels of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +3 levels of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +4 levels of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +5 levels of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +Many levels of apply! +1,2,3,4 +1,2,3,4 +1,2,3,4 +different invocations that'll trigger relinking +1,2,3,4 +1,2,3,4 +5,6,7,8 +8-7-6-5 +Many levels of call! +1,2,3,4 +1,2,3,4 +1,2,3,4 +call apply call apply call... a lot +1,2,3,4 +1,2,3,4 +1,2,3,4 +apply call apply call apply... a lot +1,2,3,4 +1,2,3,4 +1,2,3,4 diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8047057.js --- a/nashorn/test/script/basic/JDK-8047057.js Wed Jul 05 19:47:10 2017 +0200 +++ b/nashorn/test/script/basic/JDK-8047057.js Tue Jul 01 14:27:28 2014 -0700 @@ -29,8 +29,7 @@ */ // commented out makeFuncAndCall calls are still result in crash -// Tests commented with //** fail only within test framework. -// Pass fine with standalone "jjs" mode. +// Tests commented with //** fail only when assertions are turned on function makeFuncAndCall(code) { Function(code)(); @@ -46,30 +45,31 @@ } } -// makeFuncAndCall("switch(0) { default: {break;} return }"); -// makeFuncAndCall("L: { { break L; } return; }"); +makeFuncAndCall("switch(0) { default: {break;} return }"); +makeFuncAndCall("L: { { break L; } return; }"); makeFuncAndCall("L: { while(0) break L; return; }"); makeFuncExpectError("L: {while(0) break L; return [](); }", TypeError); // makeFuncAndCall("do with({}) break ; while(0);"); makeFuncAndCall("while(0) with({}) continue ;"); -//** makeFuncAndCall("eval([]);"); -//** makeFuncAndCall("try{} finally{[]}"); +makeFuncAndCall("eval([]);"); +makeFuncAndCall("try{} finally{[]}"); makeFuncAndCall("try { } catch(x if 1) { try { } catch(x2) { } }"); makeFuncAndCall("try { } catch(x if 1) { try { return; } catch(x2) { { } } }"); makeFuncAndCall("Error() * (false)[-0]--"); makeFuncAndCall("try { var x = 1, x = null; } finally { }"); makeFuncAndCall("try { var x = {}, x = []; } catch(x3) { }"); -//** makeFuncAndCall("[delete this]"); +makeFuncAndCall("[delete this]"); // makeFuncAndCall("if(eval('', eval('', function() {}))) { }"); // makeFuncAndCall("if(eval('', eval('', function() {}))) { }"); // makeFuncAndCall("eval(\"[,,];\", [11,12,13,14].some)"); // makeFuncAndCall("eval(\"1.2e3\", ({})[ /x/ ])"); -// makeFuncAndCall("eval(\"x4\", x3);"); +makeFuncExpectError("eval(\"x4\", x3);", ReferenceError); makeFuncAndCall("with({5.0000000000000000000000: String()}){(false); }"); makeFuncAndCall("try { var x = undefined, x = 5.0000000000000000000000; } catch(x) { x = undefined; }"); makeFuncAndCall("(function (x){ x %= this}(false))"); -// makeFuncAndCall("eval.apply.apply(function(){ eval('') })"); +makeFuncAndCall("eval.apply.apply(function(){ eval('') })"); makeFuncAndCall("(false % !this) && 0"); makeFuncAndCall("with({8: 'fafafa'.replace()}){ }"); makeFuncAndCall("(function (x) '' )(true)"); makeFuncExpectError("new eval(function(){})", TypeError); +//** makeFuncAndCall('eval("23", ({})[/x/])'); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8047078.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8047078.js Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014, 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-8047078: ArrayLiteral mutability caused trouble in optimistic types + * + * @test + * @run + */ + +function makeFuncAndCall(code) { + Function(code)(); +} + +makeFuncAndCall("eval([]);"); +makeFuncAndCall("eval([1]);"); +makeFuncAndCall("eval([1,2,3,,4]);"); +makeFuncAndCall("try{} finally{[]}"); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8047357.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8047357.js Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010, 2014, 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-8047357: More precise synthetic return + unreachable throw + * + * @test + * @run + */ + +print((function() { switch(0) { default: {var x; break ; } throw x; } })()); +print((function() { switch(0) { default: {break;} return; } })()); diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8047357.js.EXPECTED --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8047357.js.EXPECTED Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,2 @@ +undefined +undefined diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8047359.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8047359.js Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014, 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-8047359: large string size RangeError should be thrown rather than reporting negative length + * + * @test + * @run + */ + +try { + var s = " "; for (var i=0;i<31;++i) s+=s; s.length; + throw new Error("should have thrown RangeError!"); +} catch (e) { + if (! (e instanceof RangeError)) { + fail("RangeError expected, got " + e); + } +} + +try { + var s = " "; for (var i=0;i<31;++i) s+=s; + throw new Error("should have thrown RangeError!"); +} catch (e) { + if (! (e instanceof RangeError)) { + fail("RangeError expected, got " + e); + } +} diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/script/basic/JDK-8047369.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8047369.js Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2014, 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-8047369: Add regression tests for passing test cases of JDK-8024971 + * + * @test + * @run + * @option -scripting + */ + +function makeFuncAndCall(code) { + Function(code)(); +} + +function makeFuncExpectError(code, ErrorType) { + try { + makeFuncAndCall(code); + } catch (e) { + if (! (e instanceof ErrorType)) { + fail(ErrorType.name + " expected, got " + e); + } + } +} + +function evalExpectError(code, ErrorType) { + try { + eval(code)(); + } catch (e) { + if (! (e instanceof ErrorType)) { + fail(ErrorType.name + " expected, got " + e); + } + } +} + +function evalExpectValue(code, value) { + if (eval(code) != value) { + fail("Expected " + value + " with eval of " + code); + } +} + +makeFuncAndCall("for(x.x in 0) {}"); +// bug JDK-8047357 +// makeFuncAndCall("switch((null >> x3)) { default: {var x; break ; }\nthrow x; }"); +makeFuncExpectError("switch(x) { case 8: break; case false: }", ReferenceError); +makeFuncAndCall("try { return true; } finally { return false; } "); +makeFuncAndCall("({ get 1e81(){} })"); +makeFuncAndCall("{var x, x3;try { return 0; } finally { return 3/0; } }"); +makeFuncExpectError("with(x ? 1e81 : (x2.constructor = 0.1)) {}", ReferenceError); +makeFuncAndCall("while(x-=1) {var x=0; }"); +makeFuncAndCall("while((x-=false) && 0) { var x = this; }"); +makeFuncAndCall("/*infloop*/while(x4-=x) var x, x4 = x1;"); +makeFuncAndCall("/*infloop*/L:while(x+=null) { this;var x = /x/g ; }"); +makeFuncAndCall("while((x1|=0.1) && 0) { var x1 = -0, functional; }"); +makeFuncAndCall("with({}) return (eval(\"arguments\"));"); + +evalExpectValue(< stream, int numberOfScripts) throws IOException { for (final Path file : stream) { diff -r 28dd0c7beb3c -r eb2479579f1e nashorn/test/src/jdk/nashorn/internal/runtime/ConsStringTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/src/jdk/nashorn/internal/runtime/ConsStringTest.java Tue Jul 01 14:27:28 2014 -0700 @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2010, 2014, 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; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import jdk.nashorn.internal.runtime.JSType; +import jdk.nashorn.internal.runtime.ScriptRuntime; +import org.testng.annotations.Test; + +/** + * Tests for JSType methods. + * + * @test + * @run testng jdk.nashorn.internal.runtime.ConsStringTest + */ +public class ConsStringTest { + + /** + * Test toString conversion + */ + @Test + public void testConsStringToString() { + final ConsString cs1 = new ConsString("b", "c"); + final ConsString cs2 = new ConsString("d", "e"); + final ConsString cs3 = new ConsString(cs1, cs2); + final ConsString cs4 = new ConsString(cs3, "f"); + final ConsString cs5 = new ConsString("a", cs4); + assertEquals(cs5.toString(), "abcdef"); + assertEquals(cs4.toString(), "bcdef"); + assertEquals(cs3.toString(), "bcde"); + assertEquals(cs2.toString(), "de"); + assertEquals(cs1.toString(), "bc"); + // ConsStrings should be flattened now + assertEquals(cs1.getComponents()[0], "bc"); + assertEquals(cs1.getComponents()[1], ""); + assertEquals(cs2.getComponents()[0], "de"); + assertEquals(cs2.getComponents()[1], ""); + assertEquals(cs3.getComponents()[0], "bcde"); + assertEquals(cs3.getComponents()[1], ""); + assertEquals(cs4.getComponents()[0], "bcdef"); + assertEquals(cs4.getComponents()[1], ""); + assertEquals(cs5.getComponents()[0], "abcdef"); + assertEquals(cs5.getComponents()[1], ""); + } + + /** + * Test charAt + */ + @Test + public void testConsStringCharAt() { + final ConsString cs1 = new ConsString("b", "c"); + final ConsString cs2 = new ConsString("d", "e"); + final ConsString cs3 = new ConsString(cs1, cs2); + final ConsString cs4 = new ConsString(cs3, "f"); + final ConsString cs5 = new ConsString("a", cs4); + assertEquals(cs1.charAt(1), 'c'); + assertEquals(cs2.charAt(0), 'd'); + assertEquals(cs3.charAt(3), 'e'); + assertEquals(cs4.charAt(1), 'c'); + assertEquals(cs5.charAt(2), 'c'); + // ConsStrings should be flattened now + assertEquals(cs1.getComponents()[0], "bc"); + assertEquals(cs1.getComponents()[1], ""); + assertEquals(cs2.getComponents()[0], "de"); + assertEquals(cs2.getComponents()[1], ""); + assertEquals(cs3.getComponents()[0], "bcde"); + assertEquals(cs3.getComponents()[1], ""); + assertEquals(cs4.getComponents()[0], "bcdef"); + assertEquals(cs4.getComponents()[1], ""); + assertEquals(cs5.getComponents()[0], "abcdef"); + assertEquals(cs5.getComponents()[1], ""); + } + + + /** + * Test flattening of top-level and internal ConsStrings + */ + @Test + public void testConsStringFlattening() { + final ConsString cs1 = new ConsString("b", "c"); + final ConsString cs2 = new ConsString("d", "e"); + final ConsString cs3 = new ConsString(cs1, cs2); + final ConsString cs4 = new ConsString(cs3, "f"); + + final ConsString cs5 = new ConsString("a", cs4); + // top-level ConsString should not yet be flattened + assert(cs5.getComponents()[0] == "a"); + assert(cs5.getComponents()[1] == cs4); + assertEquals(cs5.toString(), "abcdef"); + // top-level ConsString should be flattened + assertEquals(cs5.getComponents()[0], "abcdef"); + assertEquals(cs5.getComponents()[1], ""); + // internal ConsString should not yet be flattened after first traversal + assertEquals(cs4.getComponents()[0], cs3); + assertEquals(cs4.getComponents()[1], "f"); + + final ConsString cs6 = new ConsString("a", cs4); + // top-level ConsString should not yet be flattened + assertEquals(cs6.getComponents()[0], "a"); + assertEquals(cs6.getComponents()[1], cs4); + assertEquals(cs6.toString(), "abcdef"); + // top-level ConsString should be flattened + assertEquals(cs6.getComponents()[0], "abcdef"); + assertEquals(cs6.getComponents()[1], ""); + // internal ConsString should have been flattened after second traversal + assertEquals(cs4.getComponents()[0], "bcdef"); + assertEquals(cs4.getComponents()[1], ""); + } +}