diff --git a/nashorn/bin/jjsdebug.sh b/nashorn/bin/jjsdebug.sh
new file mode 100644
index 00000000000..509700c39bf
--- /dev/null
+++ b/nashorn/bin/jjsdebug.sh
@@ -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 --git a/nashorn/make/build.xml b/nashorn/make/build.xml
index 1b28e9bdcbe..a872f358e6d 100644
--- a/nashorn/make/build.xml
+++ b/nashorn/make/build.xml
@@ -125,8 +125,7 @@
encoding="${javac.encoding}"
includeantruntime="false" fork="true">
-
-
+
diff --git a/nashorn/make/nbproject/ide-targets.xml b/nashorn/make/nbproject/ide-targets.xml
index a592cff6a07..d1e8135f101 100644
--- a/nashorn/make/nbproject/ide-targets.xml
+++ b/nashorn/make/nbproject/ide-targets.xml
@@ -34,7 +34,7 @@
-
+
diff --git a/nashorn/make/project.properties b/nashorn/make/project.properties
index eb708a41320..77f8ed73bf7 100644
--- a/nashorn/make/project.properties
+++ b/nashorn/make/project.properties
@@ -279,6 +279,7 @@ run.test.jvmargs.common=\
-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 --git a/nashorn/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java b/nashorn/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java
index 9e3fbe8231f..dab6cbf546e 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java
@@ -290,7 +290,7 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
return new CallerSensitiveDynamicMethod(m);
}
final Member member = (Member)m;
- return new SimpleDynamicMethod(unreflectSafely(m), member.getDeclaringClass(), member.getName());
+ return new SimpleDynamicMethod(unreflectSafely(m), member.getDeclaringClass(), member.getName(), m instanceof Constructor);
}
/**
@@ -390,6 +390,10 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
return new GuardedInvocationComponent(invocation, getClassGuard(type), clazz, ValidationType.EXACT_CLASS);
}
+ SingleDynamicMethod getConstructorMethod(final String signature) {
+ return null;
+ }
+
private MethodHandle getAssignableGuard(final MethodType type) {
return Guards.asType(assignableGuard, type);
}
@@ -412,18 +416,18 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
return inv == null ? null : new GuardedInvocation(inv, getClassGuard(callSiteDescriptor.getMethodType()));
}
- private static MethodHandle getDynamicMethodInvocation(final CallSiteDescriptor callSiteDescriptor,
+ private MethodHandle getDynamicMethodInvocation(final CallSiteDescriptor callSiteDescriptor,
final LinkerServices linkerServices, final String methodName, final Map methodMap) {
final DynamicMethod dynaMethod = getDynamicMethod(methodName, methodMap);
return dynaMethod != null ? dynaMethod.getInvocation(callSiteDescriptor, linkerServices) : null;
}
- private static DynamicMethod getDynamicMethod(final String methodName, final Map methodMap) {
+ private DynamicMethod getDynamicMethod(final String methodName, final Map methodMap) {
final DynamicMethod dynaMethod = methodMap.get(methodName);
return dynaMethod != null ? dynaMethod : getExplicitSignatureDynamicMethod(methodName, methodMap);
}
- private static SingleDynamicMethod getExplicitSignatureDynamicMethod(final String methodName,
+ private SingleDynamicMethod getExplicitSignatureDynamicMethod(final String fullName,
final Map methodsMap) {
// What's below is meant to support the "name(type, type, ...)" syntax that programmers can use in a method name
// to manually pin down an exact overloaded variant. This is not usually required, as the overloaded method
@@ -433,23 +437,33 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
// for performance reasons.
// Is the method name lexically of the form "name(types)"?
- final int lastChar = methodName.length() - 1;
- if(methodName.charAt(lastChar) != ')') {
+ final int lastChar = fullName.length() - 1;
+ if(fullName.charAt(lastChar) != ')') {
return null;
}
- final int openBrace = methodName.indexOf('(');
+ final int openBrace = fullName.indexOf('(');
if(openBrace == -1) {
return null;
}
+ final String name = fullName.substring(0, openBrace);
+ final String signature = fullName.substring(openBrace + 1, lastChar);
+
// Find an existing method for the "name" part
- final DynamicMethod simpleNamedMethod = methodsMap.get(methodName.substring(0, openBrace));
+ final DynamicMethod simpleNamedMethod = methodsMap.get(name);
if(simpleNamedMethod == null) {
+ // explicit signature constructor access
+ // Java.type("java.awt.Color")["(int,int,int)"]
+ // will get Color(int,int,int) constructor of Color class.
+ if (name.isEmpty()) {
+ return getConstructorMethod(signature);
+ }
+
return null;
}
// Try to get a narrowed dynamic method for the explicit parameter types.
- return simpleNamedMethod.getMethodForExactParamTypes(methodName.substring(openBrace + 1, lastChar));
+ return simpleNamedMethod.getMethodForExactParamTypes(signature);
}
private static final MethodHandle IS_METHOD_HANDLE_NOT_NULL = Guards.isNotNull().asType(MethodType.methodType(
diff --git a/nashorn/src/jdk/internal/dynalink/beans/BeansLinker.java b/nashorn/src/jdk/internal/dynalink/beans/BeansLinker.java
index b1862de0f0c..439c5a6519f 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/BeansLinker.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/BeansLinker.java
@@ -168,6 +168,26 @@ public class BeansLinker implements GuardingDynamicLinker {
return obj instanceof DynamicMethod;
}
+ /**
+ * Returns true if the object is a Dynalink Java constructor.
+ *
+ * @param obj the object we want to test for being a constructor
+ * @return true if it is a constructor, false otherwise.
+ */
+ public static boolean isDynamicConstructor(final Object obj) {
+ return obj instanceof DynamicMethod && ((DynamicMethod)obj).isConstructor();
+ }
+
+ /**
+ * Return the dynamic method of constructor of the given class and the given signature.
+ * @param clazz the class
+ * @param signature full signature of the constructor
+ * @return DynamicMethod for the constructor
+ */
+ public static Object getConstructorMethod(final Class> clazz, final String signature) {
+ return StaticClassLinker.getConstructorMethod(clazz, signature);
+ }
+
/**
* Returns a collection of names of all readable instance properties of a class.
* @param clazz the class
diff --git a/nashorn/src/jdk/internal/dynalink/beans/CallerSensitiveDynamicMethod.java b/nashorn/src/jdk/internal/dynalink/beans/CallerSensitiveDynamicMethod.java
index 5fceb1a7af0..2896732e3f7 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/CallerSensitiveDynamicMethod.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/CallerSensitiveDynamicMethod.java
@@ -155,4 +155,9 @@ class CallerSensitiveDynamicMethod extends SingleDynamicMethod {
return StaticClassIntrospector.editConstructorMethodHandle(Lookup.unreflectConstructor(lookup,
(Constructor>)target));
}
+
+ @Override
+ boolean isConstructor() {
+ return target instanceof Constructor;
+ }
}
diff --git a/nashorn/src/jdk/internal/dynalink/beans/DynamicMethod.java b/nashorn/src/jdk/internal/dynalink/beans/DynamicMethod.java
index e72ca6dce54..7b7a4d80130 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/DynamicMethod.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/DynamicMethod.java
@@ -147,4 +147,13 @@ abstract class DynamicMethod {
public String toString() {
return "[" + getClass().getName() + " " + getName() + "]";
}
+
+ /**
+ * True if this method happens to be a constructor method.
+ *
+ * @return true if this represents a constructor.
+ */
+ boolean isConstructor() {
+ return false;
+ }
}
diff --git a/nashorn/src/jdk/internal/dynalink/beans/DynamicMethodLinker.java b/nashorn/src/jdk/internal/dynalink/beans/DynamicMethodLinker.java
index 08f2a2581c1..7067c2ce236 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/DynamicMethodLinker.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/DynamicMethodLinker.java
@@ -114,15 +114,30 @@ class DynamicMethodLinker implements TypeBasedGuardingDynamicLinker {
return null;
}
final String operator = desc.getNameToken(CallSiteDescriptor.OPERATOR);
- if(operator == "call") {
- final MethodHandle invocation = ((DynamicMethod)receiver).getInvocation(
+ final DynamicMethod dynMethod = (DynamicMethod)receiver;
+ final boolean constructor = dynMethod.isConstructor();
+ final MethodHandle invocation;
+
+ if (operator == "call" && !constructor) {
+ invocation = dynMethod.getInvocation(
CallSiteDescriptorFactory.dropParameterTypes(desc, 0, 1), linkerServices);
- if(invocation == null) {
+ } else if (operator == "new" && constructor) {
+ final MethodHandle ctorInvocation = dynMethod.getInvocation(desc, linkerServices);
+ if(ctorInvocation == null) {
return null;
}
- return new GuardedInvocation(MethodHandles.dropArguments(invocation, 0,
- desc.getMethodType().parameterType(0)), Guards.getIdentityGuard(receiver));
+
+ // Insert null for StaticClass parameter
+ invocation = MethodHandles.insertArguments(ctorInvocation, 0, (Object)null);
+ } else {
+ return null;
}
+
+ if (invocation != null) {
+ return new GuardedInvocation(MethodHandles.dropArguments(invocation, 0,
+ desc.getMethodType().parameterType(0)), Guards.getIdentityGuard(receiver));
+ }
+
return null;
}
}
diff --git a/nashorn/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java b/nashorn/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java
index 7dc21a2978b..83156e3b136 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java
@@ -236,6 +236,12 @@ class OverloadedDynamicMethod extends DynamicMethod {
return false;
}
+ @Override
+ public boolean isConstructor() {
+ assert !methods.isEmpty();
+ return methods.getFirst().isConstructor();
+ }
+
ClassLoader getClassLoader() {
return classLoader;
}
@@ -303,6 +309,11 @@ class OverloadedDynamicMethod extends DynamicMethod {
* @param method a method to add
*/
public void addMethod(final SingleDynamicMethod method) {
+ assert constructorFlagConsistent(method);
methods.add(method);
}
+
+ private boolean constructorFlagConsistent(final SingleDynamicMethod method) {
+ return methods.isEmpty()? true : (methods.getFirst().isConstructor() == method.isConstructor());
+ }
}
diff --git a/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java b/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java
index 75332859f17..70ec495a7ac 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java
@@ -152,7 +152,7 @@ class OverloadedMethod {
@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 --git a/nashorn/src/jdk/internal/dynalink/beans/SimpleDynamicMethod.java b/nashorn/src/jdk/internal/dynalink/beans/SimpleDynamicMethod.java
index 2965cb4e401..f4a8b0a0b5a 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/SimpleDynamicMethod.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/SimpleDynamicMethod.java
@@ -98,6 +98,7 @@ import java.lang.invoke.MethodType;
*/
class SimpleDynamicMethod extends SingleDynamicMethod {
private final MethodHandle target;
+ private final boolean constructor;
/**
* Creates a new simple dynamic method, with a name constructed from the class name, method name, and handle
@@ -108,8 +109,22 @@ class SimpleDynamicMethod extends SingleDynamicMethod {
* @param name the simple name of the method
*/
SimpleDynamicMethod(final MethodHandle target, final Class> clazz, final String name) {
+ this(target, clazz, name, false);
+ }
+
+ /**
+ * Creates a new simple dynamic method, with a name constructed from the class name, method name, and handle
+ * signature.
+ *
+ * @param target the target method handle
+ * @param clazz the class declaring the method
+ * @param name the simple name of the method
+ * @param constructor does this represent a constructor?
+ */
+ SimpleDynamicMethod(final MethodHandle target, final Class> clazz, final String name, final boolean constructor) {
super(getName(target, clazz, name));
this.target = target;
+ this.constructor = constructor;
}
private static String getName(final MethodHandle target, final Class> clazz, final String name) {
@@ -130,4 +145,9 @@ class SimpleDynamicMethod extends SingleDynamicMethod {
MethodHandle getTarget(final Lookup lookup) {
return target;
}
+
+ @Override
+ boolean isConstructor() {
+ return constructor;
+ }
}
diff --git a/nashorn/src/jdk/internal/dynalink/beans/StaticClassLinker.java b/nashorn/src/jdk/internal/dynalink/beans/StaticClassLinker.java
index 41955bb9e39..ab9c884a7ad 100644
--- a/nashorn/src/jdk/internal/dynalink/beans/StaticClassLinker.java
+++ b/nashorn/src/jdk/internal/dynalink/beans/StaticClassLinker.java
@@ -160,6 +160,15 @@ class StaticClassLinker implements TypeBasedGuardingDynamicLinker {
}
return null;
}
+
+ @Override
+ SingleDynamicMethod getConstructorMethod(final String signature) {
+ return constructor != null? constructor.getMethodForExactParamTypes(signature) : null;
+ }
+ }
+
+ static Object getConstructorMethod(final Class> clazz, final String signature) {
+ return linkers.get(clazz).getConstructorMethod(signature);
}
static Collection getReadableStaticPropertyNames(final Class> clazz) {
diff --git a/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java b/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java
index 814fc6936bf..91946969370 100644
--- a/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java
+++ b/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java
@@ -111,7 +111,7 @@ public class CompositeTypeBasedGuardingDynamicLinker implements TypeBasedGuardin
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 @@ public class CompositeTypeBasedGuardingDynamicLinker implements TypeBasedGuardin
}
}
+ @SuppressWarnings("fallthrough")
@Override
protected List computeValue(final Class> clazz) {
List list = NO_LINKER;
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java
index 4a817fae3db..0659e3f5c3e 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java
@@ -209,7 +209,7 @@ final class AssignSymbols extends NodeOperatorVisitor implements
if (varNode.isFunctionDeclaration()) {
symbol.setIsFunctionDeclaration();
}
- return varNode.setName((IdentNode)ident.setSymbol(symbol));
+ return varNode.setName(ident.setSymbol(symbol));
}
return varNode;
}
@@ -217,7 +217,7 @@ final class AssignSymbols extends NodeOperatorVisitor implements
}
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 class AssignSymbols extends NodeOperatorVisitor implements
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 class AssignSymbols extends NodeOperatorVisitor implements
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 @@ final class AssignSymbols extends NodeOperatorVisitor implements
// 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 --git a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
index 21b51b334c3..c890208879c 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
@@ -145,6 +145,7 @@ import jdk.nashorn.internal.runtime.PropertyMap;
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 @@ final class CodeGenerator extends NodeOperatorVisitor 0; // together with count == 0 check, asserts nonnegative count
- assert method.hasScope();
+ if (!method.hasScope()) {
+ // We can sometimes invoke this method even if the method has no slot for the scope object. Typical example:
+ // for(;;) { with({}) { break; } }. WithNode normally creates a scope, but if it uses no identifiers and
+ // nothing else forces creation of a scope in the method, we just won't have the :scope local variable.
+ return;
+ }
method.loadCompilerConstant(SCOPE);
for(int i = 0; i < count; ++i) {
method.invoke(ScriptObject.GET_PROTO);
@@ -1277,13 +1295,26 @@ final class CodeGenerator extends NodeOperatorVisitor(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 @@ enum CompilationPhase {
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 @@ enum CompilationPhase {
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 @@ enum CompilationPhase {
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 @@ enum CompilationPhase {
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 --git a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
index 5c7757a1fa7..f5281ba9d56 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
@@ -46,10 +46,10 @@ import java.util.LinkedList;
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;
@@ -122,6 +122,11 @@ public final class Compiler implements Loggable {
*/
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 @@ public final class Compiler implements Loggable {
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 @@ public final class Compiler implements Loggable {
* @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 @@ public final class Compiler implements Loggable {
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 @@ public final class Compiler implements Loggable {
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 @@ public final class Compiler implements Loggable {
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 @@ public final class Compiler implements Loggable {
time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L);
}
+ if(typeInformationFile != null && !phases.isRestOfCompilation()) {
+ OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints);
+ }
+
log.unindent();
if (info) {
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java b/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java
index 7e461088747..86dcd772db3 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java
@@ -62,6 +62,8 @@ public abstract class FieldObjectCreator extends ObjectCreator {
/** 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 @@ public abstract class FieldObjectCreator extends ObjectCreator {
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 @@ public abstract class FieldObjectCreator extends ObjectCreator {
@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 --git a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
index 627623b378e..5e06794609c 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
@@ -141,7 +141,7 @@ public final class FunctionSignature {
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 --git a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java
index 5a1f09ae1ec..df763c1ba4f 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java
@@ -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.LabelNode;
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.ReturnNode;
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 @@ final class LocalVariableTypesCalculator extends NodeVisitor{
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 @@ final class LocalVariableTypesCalculator extends NodeVisitor{
}
}
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 @@ final class LocalVariableTypesCalculator extends NodeVisitor{
@Override
public boolean enterThrowNode(final ThrowNode throwNode) {
+ if(!reachable) {
+ return false;
+ }
+
throwNode.getExpression().accept(this);
jumpToCatchBlock(throwNode);
doesNotContinueSequentially();
@@ -1027,6 +1039,15 @@ final class LocalVariableTypesCalculator extends NodeVisitor{
@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 @@ final class LocalVariableTypesCalculator extends NodeVisitor{
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 @@ final class LocalVariableTypesCalculator extends NodeVisitor{
} 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;
}
@@ -1173,6 +1213,16 @@ final class LocalVariableTypesCalculator extends NodeVisitor{
return node;
}
+ @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;
@@ -1207,10 +1257,10 @@ final class LocalVariableTypesCalculator extends NodeVisitor{
@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 --git a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
index 3da0f778712..352270d05dd 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
@@ -75,7 +75,6 @@ import jdk.nashorn.internal.parser.TokenType;
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;
@@ -159,30 +158,6 @@ final class Lower extends NodeOperatorVisitor implements Lo
return context.getLogger(this.getClass());
}
- @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);
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java b/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java
index 8e7cfa3fd78..d4800f8a892 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java
@@ -63,13 +63,13 @@ public class MapCreator {
/**
* 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 @@ public class MapCreator {
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 @@ public class MapCreator {
//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 @@ public class MapCreator {
*
* @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 @@ public class MapCreator {
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 --git a/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java
index 1c0ee63703e..815ce271bf6 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java
@@ -550,7 +550,7 @@ public final class ObjectClassGenerator implements Loggable {
}
//no optimism here. we do unconditional conversion to types
- private static MethodHandle createGetterInner(final Class> forType, final Class> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final MethodHandle[] converters, final int programPoint) {
+ private static MethodHandle createGetterInner(final Class> forType, final Class> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final List converters, final int programPoint) {
final int fti = forType == null ? TYPE_UNDEFINED_INDEX : getAccessorTypeIndex(forType);
final int ti = getAccessorTypeIndex(type);
//this means fail if forType != type
@@ -564,7 +564,7 @@ public final class ObjectClassGenerator implements Loggable {
if (isOptimistic) {
//return undefined if asking for object. otherwise throw UnwarrantedOptimismException
if (ti == TYPE_OBJECT_INDEX) {
- return MH.dropArguments(GET_UNDEFINED[TYPE_OBJECT_INDEX], 0, Object.class);
+ return MH.dropArguments(GET_UNDEFINED.get(TYPE_OBJECT_INDEX), 0, Object.class);
}
//throw exception
return MH.asType(
@@ -578,7 +578,7 @@ public final class ObjectClassGenerator implements Loggable {
getter.type().changeReturnType(type));
}
//return an undefined and coerce it to the appropriate type
- return MH.dropArguments(GET_UNDEFINED[ti], 0, Object.class);
+ return MH.dropArguments(GET_UNDEFINED.get(ti), 0, Object.class);
}
assert forType != null;
@@ -604,7 +604,7 @@ public final class ObjectClassGenerator implements Loggable {
return MH.filterReturnValue(
objectGetter,
MH.insertArguments(
- converters[ti],
+ converters.get(ti),
1,
programPoint));
}
@@ -631,7 +631,7 @@ public final class ObjectClassGenerator implements Loggable {
final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter);
if (fti == TYPE_OBJECT_INDEX) {
if (fti != ti) {
- return MH.filterReturnValue(tgetter, CONVERT_OBJECT[ti]);
+ return MH.filterReturnValue(tgetter, CONVERT_OBJECT.get(ti));
}
return tgetter;
}
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java b/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java
new file mode 100644
index 00000000000..986795f0604
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java
@@ -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(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