diff options
362 files changed, 9904 insertions, 2962 deletions
@@ -308,6 +308,8 @@ ad36f9454ce38d78be39fc819902e1223765ee5e jdk8u20-b23 d3da140e179343011017669a6dbfcc52b0e56f52 jdk8u20-b24 d3da140e179343011017669a6dbfcc52b0e56f52 jdk8u20-b25 a23ac9db4227d78b3389e01fa94a8cb695a8fb0a jdk8u20-b26 +aa30541c5f0db0d03ae6625268642ac71f59c4e6 jdk8u20-b31 +bc4b5edeb8268a75718e65a84864f09c95b3032c jdk8u20-b32 7001e9f95b443a75e432205a29974c05b88e0fdc jdk8u25-b00 a9f77bd14874d5f8fdf935704dd54a0451f2bc69 jdk8u25-b01 895e47783e2ee6823496a5ae84039a4f50311c7d jdk8u25-b02 @@ -327,6 +329,21 @@ f76715cd4e902602bdbb4ba9a3774c10afeee012 jdk8u25-b12 1500138ce513600457be6bfa10979ecce6515aa6 jdk8u25-b16 4b9cc65dd24d398c4f921c0beccfb8caeaaaf584 jdk8u25-b17 cdbf34dbef404b47805c8c85b11c65c2afaa6674 jdk8u25-b18 +4f9e65387c21831d0ea5726641a302c2ce73a4cc jdk8u25-b31 +9b692a6e5f22228f822973d35610d37cb9dd9693 jdk8u31-b00 +6bf53bb6c969678488b1c073d56dd55df1a0ea17 jdk8u31-b01 +809bf97d7e70dcb3873fcbc10f12f62580b1c11d jdk8u31-b02 +3505d266634ded89bf9617ff6b385ab8a52f78cf jdk8u31-b03 +96acff2ad9e19aa80c4f7ed60d87a422bca1ea91 jdk8u31-b04 +5fc3f210872d365c57ed4e8dba3926d9ed5c7e45 jdk8u31-b05 +99a3333f7f8489bb3c80f0c0643ae19e549a0941 jdk8u31-b06 +5ed4fa732b26b6d8e37dfc5bbd00047c5352719b jdk8u31-b07 +b17ecf341ee544cc5507b9b586c14a13c3adc058 jdk8u31-b08 +762eaacc45cec3f7d593bedd08fb8de478d4415b jdk8u31-b09 +c68ba913a0eeea6eb94d9568e9985505ec3408a3 jdk8u31-b10 +599bd596fa549d882aa8fc5104c322a75a3af728 jdk8u31-b11 +f36c71a03e4ed467f630cc46d076a5bb4c58b6d5 jdk8u31-b12 +ec36fa3b35eb00f053d624ae837579c6b8e446ac jdk8u31-b13 f2925491b61b22ac42f8c30ee9c6723ffa401a4c jdk8u40-b00 62468d841b842769d875bd97d10370585c296eb7 jdk8u40-b01 bdd9c38d1e61edbf770b8733b70a37d0cf0e7055 jdk8u40-b02 @@ -342,3 +359,11 @@ a2e0a985764b5afd5f316429bfab4f44bf150f7f jdk8u40-b03 4f97f0da29eb78c8234ada7b7cb874a494ee653e jdk8u40-b12-aarch64 af0397959d778072cca638774c9b53ed3787fcfb jdk8u40-b12-aarch64-1262 e67a19f29c7095700105ca2b09b8bb21ad02e078 jdk8u40-b12-aarch64-1263 +fc37699ddc0ed41d4ab5da821211a6d2648c8883 jdk8u40-b15 +e079f3f6d536510b1ab3589b1038d893d78302ac jdk8u40-b16 +88e22262fdb26e3154a1034c2413415e97b9a86a jdk8u40-b17 +653739706172ae94e999731a3a9f10f8ce11ffca jdk8u40-b18 +6ec61d2494283fbaca6df227f1a5b45487dc1ca7 jdk8u40-b19 +4d240320929f7b2247eeb97e43efe2370b70582e jdk8u40-b20 +dbb663a9d9aa2807ef501c7d20f29415816a1973 jdk8u40-b21 +f9f70a0f60f48fbb95275b6c1110cedf740c6177 jdk8u40-b22 diff --git a/THIRD_PARTY_README b/THIRD_PARTY_README index 6d1c60f2..c34ce6b3 100644 --- a/THIRD_PARTY_README +++ b/THIRD_PARTY_README @@ -3385,7 +3385,7 @@ with JRE 8, JDK 8, and OpenJDK 8. included with JRE 8, JDK 8, and OpenJDK 8. Apache Commons Math 3.2 - Apache Derby 10.10.1.3 + Apache Derby 10.11.1.2 Apache Jakarta BCEL 5.1 Apache Jakarta Regexp 1.4 Apache Santuario XML Security for Java 1.5.4 diff --git a/bin/runopt.sh b/bin/runopt.sh index 6b26e287..5ec9046b 100644 --- a/bin/runopt.sh +++ b/bin/runopt.sh @@ -28,14 +28,35 @@ # known flags for performance for the current configration ########################################################################################### +# Flags to enable assertions, we need the system assertions too, since +# this script runs Nashorn in the BCP to override any nashorn.jar that might +# reside in your $JAVA_HOME/jre/lib/ext/nashorn.jar +# +ENABLE_ASSERTIONS_FLAGS="-ea -esa" + # Flags to instrument lambdaform computation, caching, interpretation and compilation # Default compile threshold for lambdaforms is 30 -#FLAGS="-Djava.lang.invoke.MethodHandle.COMPILE_THRESHOLD=3 -Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true -Djava.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE=true -Djava.lang.invoke.MethodHandle.TRACE_INTERPRETER=true" - +# +#LAMBDAFORM_FLAGS="\ +# -Djava.lang.invoke.MethodHandle.COMPILE_THRESHOLD=3 \ +# -Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true \ +# -Djava.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE=true \ +# -Djava.lang.invoke.MethodHandle.TRACE_INTERPRETER=true" # Flags to run trusted tests from the Nashorn test suite -#FLAGS="-Djava.security.manager -Djava.security.policy=../build/nashorn.policy -Dnashorn.debug" +# +#TRUSTED_TEST_FLAGS="\ +#-Djava.security.manager \ +#-Djava.security.policy=../build/nashorn.policy -Dnashorn.debug" +# Testing out new code optimizations using the generic hotspot "new code" parameter +# +#USE_NEW_CODE_FLAGS=-XX:+UnlockDiagnosticVMOptions -XX:+UseNewCode + +# +#-Dnashorn.typeInfo.disabled=false \ +# and for Nashorn options: +# --class-cache-size=0 --persistent-code-cache=false # Unique timestamped file name for JFR recordings. For JFR, we also have to # crank up the stack cutoff depth to 1024, because of ridiculously long lambda form @@ -46,8 +67,42 @@ # can go (10 ms on most platforms). The default is normally higher. The increased # sampling overhead is usually negligible for Nashorn runs, but the data is better -JFR_FILENAME="./nashorn_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" +if [ -z $JFR_FILENAME ]; then + JFR_FILENAME="./nashorn_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" +fi + +# Flight recorder +# +# see above - already in place, copy the flags down here to disable +ENABLE_FLIGHT_RECORDER_FLAGS="\ + -XX:+UnlockCommercialFeatures \ + -XX:+FlightRecorder \ + -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$JFR_FILENAME,stackdepth=1024" + +# Type specialization and math intrinsic replacement should be enabled by default in 8u20 and nine, +# keeping this flag around for experimental reasons. Replace + with - to switch it off +# +#ENABLE_TYPE_SPECIALIZATION_FLAGS=-XX:+UseTypeSpeculation + +# Same with math intrinsics. They should be enabled by default in 8u20 and 9, so +# this disables them if needed +# +#DISABLE_MATH_INTRINSICS_FLAGS=-XX:-UseMathExactIntrinsics + +# Add timing to time the compilation phases. +#ENABLE_TIME_FLAGS=--log=time + +# Add ShowHiddenFrames to get lambda form internals on the stack traces +#ENABLE_SHOW_HIDDEN_FRAMES_FLAGS=-XX:+ShowHiddenFrames + +# Add print optoassembly to get an asm dump. This requires 1) a debug build, not product, +# That tired compilation is switched off, for C2 only output and that the number of +# compiler threads is set to 1 for determinsm. +# +#PRINT_ASM_FLAGS=-XX:+PrintOptoAssembly -XX:-TieredCompilation -XX:CICompilerCount=1 \ +# Tier compile threasholds. Default value is 10. (1-100 is useful for experiments) +#TIER_COMPILATION_THRESHOLD_FLAGS=-XX:IncreaseFirstTierCompileThresholdAt=10 # Directory where to look for nashorn.jar in a dist folder. The default is "..", assuming # that we run the script from the make dir @@ -59,49 +114,23 @@ NASHORN_JAR=$DIR/dist/nashorn.jar # nashorn.jar in $JAVA_HOME/jre/lib/ext. Thus, we also need -esa, as assertions in # nashorn count as system assertions in this configuration +# Type profiling default level is 111, 222 adds some compile time, but is faster + $JAVA_HOME/bin/java \ -$FLAGS \ --ea \ --esa \ +$ENABLE_ASSERTIONS_FLAGS \ +$LAMBDAFORM_FLAGS \ +$TRUSTED_FLAGS \ +$USE_NEW_CODE_FLAGS \ +$ENABLE_SHOW_HIDDEN_FRAMES_FLAGS \ +$ENABLE_FLIGHT_RECORDER_FLAGS \ +$ENABLE_TYPE_SPECIALIZATION_FLAGS \ +$TIERED_COMPILATION_THRESOLD_FLAGS \ +$DISABLE_MATH_INTRINSICS_FLAGS \ +$PRINT_ASM_FLAGS \ -Xbootclasspath/p:$NASHORN_JAR \ -Xms2G -Xmx2G \ +-XX:TypeProfileLevel=222 \ -cp $CLASSPATH:../build/test/classes/ \ -jdk.nashorn.tools.Shell ${@} - -# Below are flags that may come in handy, but aren't used for default runs - - -# Type profiling default level is 111, 222 adds some compile time, but produces better code. -# -XX:TypeProfileLevel=222 \ - - -# Testing out new code optimizations using the generic hotspot "new code" parameter -#-XX:+UnlockDiagnosticVMOptions \ -#-XX:+UseNewCode \ - - -# Type specialization and math intrinsic replacement should be enabled by default in 8u20 and nine, -# keeping this flag around for experimental reasons. Replace + with - to switch it off -#-XX:+UseTypeSpeculation \ - +jdk.nashorn.tools.Shell $ENABLE_TIME_FLAGS ${@} -# Same with math intrinsics. They should be enabled by default in 8u20 and 9 -#-XX:+UseMathExactIntrinsics \ - - -# Add -Dnashorn.time to time the compilation phases. -#-Dnashorn.time \ - - -# Add ShowHiddenFrames to get lambda form internals on the stack traces -#-XX:+ShowHiddenFrames \ - - -# Add print optoassembly to get an asm dump. This requires 1) a debug build, not product, -# That tired compilation is switched off, for C2 only output and that the number of -# compiler threads is set to 1 for determinsm. -#-XX:+PrintOptoAssembly -XX:-TieredCompilation -XX:CICompilerCount=1 \ - -# Tier compile threasholds. Default value is 10. (1-100 is useful for experiments) -# -XX:IncreaseFirstTierCompileThresholdAt=XX diff --git a/docs/source/EvalFile.java b/docs/source/EvalFile.java index 6bb4e381..b12e91bb 100644 --- a/docs/source/EvalFile.java +++ b/docs/source/EvalFile.java @@ -29,14 +29,16 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import javax.script.*; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +@SuppressWarnings("javadoc") public class EvalFile { - public static void main(String[] args) throws Exception { + public static void main(final String[] args) throws Exception { // create a script engine manager - ScriptEngineManager factory = new ScriptEngineManager(); + final ScriptEngineManager factory = new ScriptEngineManager(); // create JavaScript engine - ScriptEngine engine = factory.getEngineByName("nashorn"); + final ScriptEngine engine = factory.getEngineByName("nashorn"); // evaluate JavaScript code from given file - specified by first argument engine.eval(new java.io.FileReader(args[0])); } diff --git a/docs/source/EvalScript.java b/docs/source/EvalScript.java index 7fcbe1cd..49f581de 100644 --- a/docs/source/EvalScript.java +++ b/docs/source/EvalScript.java @@ -29,14 +29,16 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import javax.script.*; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +@SuppressWarnings("javadoc") public class EvalScript { - public static void main(String[] args) throws Exception { + public static void main(final String[] args) throws Exception { // create a script engine manager - ScriptEngineManager factory = new ScriptEngineManager(); + final ScriptEngineManager factory = new ScriptEngineManager(); // create a JavaScript engine - ScriptEngine engine = factory.getEngineByName("nashorn"); + final ScriptEngine engine = factory.getEngineByName("nashorn"); // evaluate JavaScript code from String engine.eval("print('Hello, World')"); } diff --git a/docs/source/InvokeScriptFunction.java b/docs/source/InvokeScriptFunction.java index 26de36c4..5ce1cae0 100644 --- a/docs/source/InvokeScriptFunction.java +++ b/docs/source/InvokeScriptFunction.java @@ -29,22 +29,25 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import javax.script.*; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +@SuppressWarnings("javadoc") public class InvokeScriptFunction { - public static void main(String[] args) throws Exception { - ScriptEngineManager manager = new ScriptEngineManager(); - ScriptEngine engine = manager.getEngineByName("nashorn"); + public static void main(final String[] args) throws Exception { + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine engine = manager.getEngineByName("nashorn"); // JavaScript code in a String - String script = "function hello(name) { print('Hello, ' + name); }"; + final String script = "function hello(name) { print('Hello, ' + name); }"; // evaluate script engine.eval(script); // javax.script.Invocable is an optional interface. // Check whether your script engine implements or not! // Note that the JavaScript engine implements Invocable interface. - Invocable inv = (Invocable) engine; + final Invocable inv = (Invocable) engine; // invoke the global function named "hello" inv.invokeFunction("hello", "Scripting!!" ); diff --git a/docs/source/InvokeScriptMethod.java b/docs/source/InvokeScriptMethod.java index a3f5ece7..d3cc788d 100644 --- a/docs/source/InvokeScriptMethod.java +++ b/docs/source/InvokeScriptMethod.java @@ -29,26 +29,29 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import javax.script.*; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +@SuppressWarnings("javadoc") public class InvokeScriptMethod { - public static void main(String[] args) throws Exception { - ScriptEngineManager manager = new ScriptEngineManager(); - ScriptEngine engine = manager.getEngineByName("nashorn"); + public static void main(final String[] args) throws Exception { + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine engine = manager.getEngineByName("nashorn"); // JavaScript code in a String. This code defines a script object 'obj' // with one method called 'hello'. - String script = "var obj = new Object(); obj.hello = function(name) { print('Hello, ' + name); }"; + final String script = "var obj = new Object(); obj.hello = function(name) { print('Hello, ' + name); }"; // evaluate script engine.eval(script); // javax.script.Invocable is an optional interface. // Check whether your script engine implements or not! // Note that the JavaScript engine implements Invocable interface. - Invocable inv = (Invocable) engine; + final Invocable inv = (Invocable) engine; // get script object on which we want to call the method - Object obj = engine.get("obj"); + final Object obj = engine.get("obj"); // invoke the method named "hello" on the script object "obj" inv.invokeMethod(obj, "hello", "Script Method !!" ); diff --git a/docs/source/MultiScopes.java b/docs/source/MultiScopes.java index 6c4fa2a2..ff4f65b4 100644 --- a/docs/source/MultiScopes.java +++ b/docs/source/MultiScopes.java @@ -29,12 +29,17 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import javax.script.*; +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.SimpleScriptContext; +@SuppressWarnings("javadoc") public class MultiScopes { - public static void main(String[] args) throws Exception { - ScriptEngineManager manager = new ScriptEngineManager(); - ScriptEngine engine = manager.getEngineByName("nashorn"); + public static void main(final String[] args) throws Exception { + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine engine = manager.getEngineByName("nashorn"); engine.put("x", "hello"); // print global variable "x" @@ -42,9 +47,9 @@ public class MultiScopes { // the above line prints "hello" // Now, pass a different script context - ScriptContext newContext = new SimpleScriptContext(); + final ScriptContext newContext = new SimpleScriptContext(); newContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); - Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE); + final Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE); // add new variable "x" to the new engineScope engineScope.put("x", "world"); diff --git a/docs/source/RunnableImpl.java b/docs/source/RunnableImpl.java index 1d858d40..2b5113b1 100644 --- a/docs/source/RunnableImpl.java +++ b/docs/source/RunnableImpl.java @@ -29,28 +29,31 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import javax.script.*; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +@SuppressWarnings("javadoc") public class RunnableImpl { - public static void main(String[] args) throws Exception { - ScriptEngineManager manager = new ScriptEngineManager(); - ScriptEngine engine = manager.getEngineByName("nashorn"); + public static void main(final String[] args) throws Exception { + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine engine = manager.getEngineByName("nashorn"); // JavaScript code in a String - String script = "function run() { print('run called'); }"; + final String script = "function run() { print('run called'); }"; // evaluate script engine.eval(script); - Invocable inv = (Invocable) engine; + final Invocable inv = (Invocable) engine; // get Runnable interface object from engine. This interface methods // are implemented by script functions with the matching name. - Runnable r = inv.getInterface(Runnable.class); + final Runnable r = inv.getInterface(Runnable.class); // start a new thread that runs the script implemented // runnable interface - Thread th = new Thread(r); + final Thread th = new Thread(r); th.start(); th.join(); } diff --git a/docs/source/RunnableImplObject.java b/docs/source/RunnableImplObject.java index 877f8c15..0d5f5032 100644 --- a/docs/source/RunnableImplObject.java +++ b/docs/source/RunnableImplObject.java @@ -29,31 +29,34 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import javax.script.*; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +@SuppressWarnings("javadoc") public class RunnableImplObject { - public static void main(String[] args) throws Exception { - ScriptEngineManager manager = new ScriptEngineManager(); - ScriptEngine engine = manager.getEngineByName("nashorn"); + public static void main(final String[] args) throws Exception { + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine engine = manager.getEngineByName("nashorn"); // JavaScript code in a String - String script = "var obj = new Object(); obj.run = function() { print('run method called'); }"; + final String script = "var obj = new Object(); obj.run = function() { print('run method called'); }"; // evaluate script engine.eval(script); // get script object on which we want to implement the interface with - Object obj = engine.get("obj"); + final Object obj = engine.get("obj"); - Invocable inv = (Invocable) engine; + final Invocable inv = (Invocable) engine; // get Runnable interface object from engine. This interface methods // are implemented by script methods of object 'obj' - Runnable r = inv.getInterface(obj, Runnable.class); + final Runnable r = inv.getInterface(obj, Runnable.class); // start a new thread that runs the script implemented // runnable interface - Thread th = new Thread(r); + final Thread th = new Thread(r); th.start(); th.join(); } diff --git a/docs/source/ScriptVars.java b/docs/source/ScriptVars.java index 7e16cfca..c697431d 100644 --- a/docs/source/ScriptVars.java +++ b/docs/source/ScriptVars.java @@ -29,15 +29,17 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import javax.script.*; -import java.io.*; +import java.io.File; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +@SuppressWarnings("javadoc") public class ScriptVars { - public static void main(String[] args) throws Exception { - ScriptEngineManager manager = new ScriptEngineManager(); - ScriptEngine engine = manager.getEngineByName("nashorn"); + public static void main(final String[] args) throws Exception { + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine engine = manager.getEngineByName("nashorn"); - File f = new File("test.txt"); + final File f = new File("test.txt"); // expose File object as variable to script engine.put("file", f); diff --git a/make/build.xml b/make/build.xml index f6de5589..a7539f48 100644 --- a/make/build.xml +++ b/make/build.xml @@ -155,6 +155,7 @@ <fileset dir="${src.dir}/jdk/nashorn/tools/resources/"/> </copy> <copy file="${src.dir}/jdk/internal/dynalink/support/messages.properties" todir="${build.classes.dir}/jdk/internal/dynalink/support"/> + <copy file="${src.dir}/jdk/nashorn/internal/codegen/anchor.properties" todir="${build.classes.dir}/jdk/nashorn/internal/codegen"/> <echo message="full=${nashorn.fullversion}" file="${build.classes.dir}/jdk/nashorn/internal/runtime/resources/version.properties"/> <echo file="${build.classes.dir}/jdk/nashorn/internal/runtime/resources/version.properties" append="true">${line.separator}</echo> diff --git a/samples/browser_dom.js b/samples/browser_dom.js new file mode 100644 index 00000000..94324c7a --- /dev/null +++ b/samples/browser_dom.js @@ -0,0 +1,91 @@ +#// Usage: jjs -fx browser.js + +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +if (!$OPTIONS._fx) { + print("Usage: jjs -fx browser.js"); + exit(1); +} + +// JavaFX classes used +var ChangeListener = Java.type("javafx.beans.value.ChangeListener"); +var Scene = Java.type("javafx.scene.Scene"); +var WebView = Java.type("javafx.scene.web.WebView"); +var EventListener = Java.type("org.w3c.dom.events.EventListener"); + +// JavaFX start method +function start(stage) { + start.title = "Web View"; + var wv = new WebView(); + wv.engine.loadContent(<<EOF +<html> +<head> +<title> +This is the title +</title> +<script> +// click count for OK button +var okCount = 0; +</script> +</head> +<body> +Button from the input html<br> +<button type="button" onclick="okCount++">OK</button><br> +</body> +</html> +EOF, "text/html"); + + // attach onload handler + wv.engine.loadWorker.stateProperty().addListener( + new ChangeListener() { + changed: function() { + // DOM document element + var document = wv.engine.document; + // DOM manipulation + var btn = document.createElement("button"); + var n = 0; + // attach a button handler - nashorn function! + btn.onclick = new EventListener(function() { + n++; print("You clicked " + n + " time(s)"); + print("you clicked OK " + wv.engine.executeScript("okCount")); + }); + // attach text to button + var t = document.createTextNode("Click Me!"); + btn.appendChild(t); + // attach button to the document + document.body.appendChild(btn); + } + } + ); + stage.scene = new Scene(wv, 750, 500); + stage.show(); +} diff --git a/src/jdk/internal/dynalink/DynamicLinkerFactory.java b/src/jdk/internal/dynalink/DynamicLinkerFactory.java index af5eb119..2f4e5f33 100644 --- a/src/jdk/internal/dynalink/DynamicLinkerFactory.java +++ b/src/jdk/internal/dynalink/DynamicLinkerFactory.java @@ -97,6 +97,7 @@ import jdk.internal.dynalink.beans.BeansLinker; import jdk.internal.dynalink.linker.GuardingDynamicLinker; import jdk.internal.dynalink.linker.GuardingTypeConverterFactory; import jdk.internal.dynalink.linker.LinkRequest; +import jdk.internal.dynalink.linker.MethodTypeConversionStrategy; import jdk.internal.dynalink.support.AutoDiscovery; import jdk.internal.dynalink.support.BottomGuardingDynamicLinker; import jdk.internal.dynalink.support.ClassLoaderGetterContextProvider; @@ -105,6 +106,7 @@ import jdk.internal.dynalink.support.CompositeTypeBasedGuardingDynamicLinker; import jdk.internal.dynalink.support.DefaultPrelinkFilter; import jdk.internal.dynalink.support.LinkerServicesImpl; import jdk.internal.dynalink.support.TypeConverterFactory; +import jdk.internal.dynalink.support.TypeUtilities; /** * A factory class for creating {@link DynamicLinker}s. The most usual dynamic linker is a linker that is a composition @@ -115,7 +117,6 @@ import jdk.internal.dynalink.support.TypeConverterFactory; * @author Attila Szegedi */ public class DynamicLinkerFactory { - /** * Default value for {@link #setUnstableRelinkThreshold(int) unstable relink threshold}. */ @@ -130,6 +131,7 @@ public class DynamicLinkerFactory { private boolean syncOnRelink = false; private int unstableRelinkThreshold = DEFAULT_UNSTABLE_RELINK_THRESHOLD; private GuardedInvocationFilter prelinkFilter; + private MethodTypeConversionStrategy autoConversionStrategy; /** * Sets the class loader for automatic discovery of available linkers. If not set explicitly, then the thread @@ -259,6 +261,29 @@ public class DynamicLinkerFactory { } /** + * Sets an object representing the conversion strategy for automatic type conversions. After + * {@link TypeConverterFactory#asType(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType)} has + * applied all custom conversions to a method handle, it still needs to effect + * {@link TypeUtilities#isMethodInvocationConvertible(Class, Class) method invocation conversions} that + * can usually be automatically applied as per + * {@link java.lang.invoke.MethodHandle#asType(java.lang.invoke.MethodType)}. + * However, sometimes language runtimes will want to customize even those conversions for their own call + * sites. A typical example is allowing unboxing of null return values, which is by default prohibited by + * ordinary {@code MethodHandles.asType}. In this case, a language runtime can install its own custom + * automatic conversion strategy, that can deal with null values. Note that when the strategy's + * {@link MethodTypeConversionStrategy#asType(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType)} + * is invoked, the custom language conversions will already have been applied to the method handle, so by + * design the difference between the handle's current method type and the desired final type will always + * only be ones that can be subjected to method invocation conversions. The strategy also doesn't need to + * invoke a final {@code MethodHandle.asType()} as the converter factory will do that as the final step. + * @param autoConversionStrategy the strategy for applying method invocation conversions for the linker + * created by this factory. + */ + public void setAutoConversionStrategy(final MethodTypeConversionStrategy autoConversionStrategy) { + this.autoConversionStrategy = autoConversionStrategy; + } + + /** * Creates a new dynamic linker consisting of all the prioritized, autodiscovered, and fallback linkers as well as * the pre-link filter. * @@ -324,8 +349,9 @@ public class DynamicLinkerFactory { prelinkFilter = new DefaultPrelinkFilter(); } - return new DynamicLinker(new LinkerServicesImpl(new TypeConverterFactory(typeConverters), composite), - prelinkFilter, runtimeContextArgCount, syncOnRelink, unstableRelinkThreshold); + return new DynamicLinker(new LinkerServicesImpl(new TypeConverterFactory(typeConverters, + autoConversionStrategy), composite), prelinkFilter, runtimeContextArgCount, syncOnRelink, + unstableRelinkThreshold); } private static ClassLoader getThreadContextClassLoader() { diff --git a/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java b/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java index dab6cbf5..cf1b11d3 100644 --- a/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java +++ b/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java @@ -287,10 +287,21 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { */ private static SingleDynamicMethod createDynamicMethod(final AccessibleObject m) { if(CallerSensitiveDetector.isCallerSensitive(m)) { + // Method has @CallerSensitive annotation return new CallerSensitiveDynamicMethod(m); } + // Method has no @CallerSensitive annotation + final MethodHandle mh; + try { + mh = unreflectSafely(m); + } catch (final IllegalAccessError e) { + // java.lang.invoke can in some case conservatively treat as caller sensitive methods that aren't + // marked with the annotation. In this case, we'll fall back to treating it as caller sensitive. + return new CallerSensitiveDynamicMethod(m); + } + // Proceed with non-caller sensitive final Member member = (Member)m; - return new SimpleDynamicMethod(unreflectSafely(m), member.getDeclaringClass(), member.getName(), m instanceof Constructor); + return new SimpleDynamicMethod(mh, member.getDeclaringClass(), member.getName(), m instanceof Constructor); } /** @@ -480,8 +491,9 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { // We want setters that conform to "Object(O, V)". Note, we aren't doing "R(O, V)" as it might not be // valid for us to convert return values proactively. Also, since we don't know what setters will be - // invoked, we'll conservatively presume Object return type. - final MethodType type = callSiteDescriptor.getMethodType().changeReturnType(Object.class); + // invoked, we'll conservatively presume Object return type. The one exception is void return. + final MethodType origType = callSiteDescriptor.getMethodType(); + final MethodType type = origType.returnType() == void.class ? origType : origType.changeReturnType(Object.class); // What's below is basically: // foldArguments(guardWithTest(isNotNull, invoke, null|nextComponent.invocation), @@ -497,7 +509,7 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { // Bind property setter handle to the expected setter type and linker services. Type is // MethodHandle(Object, String, Object) final MethodHandle boundGetter = MethodHandles.insertArguments(getPropertySetterHandle, 0, - CallSiteDescriptorFactory.dropParameterTypes(callSiteDescriptor, 1, 2), linkerServices); + callSiteDescriptor.changeMethodType(setterType), linkerServices); // Cast getter to MethodHandle(O, N, V) final MethodHandle typedGetter = linkerServices.asType(boundGetter, type.changeReturnType( diff --git a/src/jdk/internal/dynalink/beans/OverloadedMethod.java b/src/jdk/internal/dynalink/beans/OverloadedMethod.java index 70ec495a..c8d50072 100644 --- a/src/jdk/internal/dynalink/beans/OverloadedMethod.java +++ b/src/jdk/internal/dynalink/beans/OverloadedMethod.java @@ -123,7 +123,6 @@ class OverloadedMethod { varArgMethods = new ArrayList<>(methodHandles.size()); final int argNum = callSiteType.parameterCount(); for(MethodHandle mh: methodHandles) { - mh = mh.asType(mh.type().changeReturnType(commonRetType)); if(mh.isVarargsCollector()) { final MethodHandle asFixed = mh.asFixedArity(); if(argNum == asFixed.type().parameterCount()) { diff --git a/src/jdk/internal/dynalink/linker/MethodTypeConversionStrategy.java b/src/jdk/internal/dynalink/linker/MethodTypeConversionStrategy.java new file mode 100644 index 00000000..f270c5fc --- /dev/null +++ b/src/jdk/internal/dynalink/linker/MethodTypeConversionStrategy.java @@ -0,0 +1,100 @@ +/* + * 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. 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. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file, and Oracle licenses the original version of this file under the BSD + * license: + */ +/* + Copyright 2014 Attila Szegedi + + Licensed under both the Apache License, Version 2.0 (the "Apache License") + and the BSD License (the "BSD License"), with licensee being free to + choose either of the two at their discretion. + + You may not use this file except in compliance with either the Apache + License or the BSD License. + + If you choose to use this file in compliance with the Apache License, the + following notice applies to you: + + You may obtain a copy of the Apache License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing + permissions and limitations under the License. + + If you choose to use this file in compliance with the BSD License, the + following notice applies to you: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package jdk.internal.dynalink.linker; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; + +/** + * Interface for objects representing a strategy for converting a method handle to a new type. + */ +public interface MethodTypeConversionStrategy { + /** + * Converts a method handle to a new type. + * @param target target method handle + * @param newType new type + * @return target converted to the new type. + */ + public MethodHandle asType(final MethodHandle target, final MethodType newType); +} diff --git a/src/jdk/internal/dynalink/support/TypeConverterFactory.java b/src/jdk/internal/dynalink/support/TypeConverterFactory.java index 79f6549b..736b787c 100644 --- a/src/jdk/internal/dynalink/support/TypeConverterFactory.java +++ b/src/jdk/internal/dynalink/support/TypeConverterFactory.java @@ -97,6 +97,7 @@ import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.GuardedTypeConversion; import jdk.internal.dynalink.linker.GuardingTypeConverterFactory; import jdk.internal.dynalink.linker.LinkerServices; +import jdk.internal.dynalink.linker.MethodTypeConversionStrategy; /** * A factory for type converters. This class is the main implementation behind the @@ -109,6 +110,7 @@ public class TypeConverterFactory { private final GuardingTypeConverterFactory[] factories; private final ConversionComparator[] comparators; + private final MethodTypeConversionStrategy autoConversionStrategy; private final ClassValue<ClassMap<MethodHandle>> converterMap = new ClassValue<ClassMap<MethodHandle>>() { @Override @@ -177,8 +179,24 @@ public class TypeConverterFactory { * Creates a new type converter factory from the available {@link GuardingTypeConverterFactory} instances. * * @param factories the {@link GuardingTypeConverterFactory} instances to compose. + * @param autoConversionStrategy conversion strategy for automatic type conversions. After + * {@link #asType(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType)} has applied all custom + * conversions to a method handle, it still needs to effect + * {@link TypeUtilities#isMethodInvocationConvertible(Class, Class) method invocation conversions} that + * can usually be automatically applied as per + * {@link java.lang.invoke.MethodHandle#asType(java.lang.invoke.MethodType)}. + * However, sometimes language runtimes will want to customize even those conversions for their own call + * sites. A typical example is allowing unboxing of null return values, which is by default prohibited by + * ordinary {@code MethodHandles.asType}. In this case, a language runtime can install its own custom + * automatic conversion strategy, that can deal with null values. Note that when the strategy's + * {@link MethodTypeConversionStrategy#asType(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType)} + * is invoked, the custom language conversions will already have been applied to the method handle, so by + * design the difference between the handle's current method type and the desired final type will always + * only be ones that can be subjected to method invocation conversions. Can be null, in which case no + * custom strategy is employed. */ - public TypeConverterFactory(final Iterable<? extends GuardingTypeConverterFactory> factories) { + public TypeConverterFactory(final Iterable<? extends GuardingTypeConverterFactory> factories, + final MethodTypeConversionStrategy autoConversionStrategy) { final List<GuardingTypeConverterFactory> l = new LinkedList<>(); final List<ConversionComparator> c = new LinkedList<>(); for(final GuardingTypeConverterFactory factory: factories) { @@ -189,20 +207,24 @@ public class TypeConverterFactory { } this.factories = l.toArray(new GuardingTypeConverterFactory[l.size()]); this.comparators = c.toArray(new ConversionComparator[c.size()]); - + this.autoConversionStrategy = autoConversionStrategy; } /** * Similar to {@link MethodHandle#asType(MethodType)} except it also hooks in method handles produced by * {@link GuardingTypeConverterFactory} implementations, providing for language-specific type coercing of - * parameters. It will apply {@link MethodHandle#asType(MethodType)} for all primitive-to-primitive, - * wrapper-to-primitive, primitive-to-wrapper conversions as well as for all upcasts. For all other conversions, - * it'll insert {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with composite filters - * provided by {@link GuardingTypeConverterFactory} implementations. + * parameters. For all conversions that are not a JLS method invocation conversion it'll insert + * {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with composite filters + * provided by {@link GuardingTypeConverterFactory} implementations. For the remaining JLS method invocation + * conversions, it will invoke {@link MethodTypeConversionStrategy#asType(MethodHandle, MethodType)} first + * if an automatic conversion strategy was specified in the + * {@link #TypeConverterFactory(Iterable, MethodTypeConversionStrategy) constructor}, and finally apply + * {@link MethodHandle#asType(MethodType)} for any remaining conversions. * * @param handle target method handle * @param fromType the types of source arguments - * @return a method handle that is a suitable combination of {@link MethodHandle#asType(MethodType)} and + * @return a method handle that is a suitable combination of {@link MethodHandle#asType(MethodType)}, + * {@link MethodTypeConversionStrategy#asType(MethodHandle, MethodType)}, and * {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with * {@link GuardingTypeConverterFactory} produced type converters as filters. */ @@ -246,8 +268,12 @@ public class TypeConverterFactory { } } - // Take care of automatic conversions - return newHandle.asType(fromType); + // Give change to automatic conversion strategy, if one is present. + final MethodHandle autoConvertedHandle = + autoConversionStrategy != null ? autoConversionStrategy.asType(newHandle, fromType) : newHandle; + + // Do a final asType for any conversions that remain. + return autoConvertedHandle.asType(fromType); } private static MethodHandle applyConverters(final MethodHandle handle, final int pos, final List<MethodHandle> converters) { diff --git a/src/jdk/internal/dynalink/support/TypeUtilities.java b/src/jdk/internal/dynalink/support/TypeUtilities.java index 6403f3d5..78d42ab1 100644 --- a/src/jdk/internal/dynalink/support/TypeUtilities.java +++ b/src/jdk/internal/dynalink/support/TypeUtilities.java @@ -118,17 +118,13 @@ public class TypeUtilities { public static Class<?> getCommonLosslessConversionType(final Class<?> c1, final Class<?> c2) { if(c1 == c2) { return c1; + } else if (c1 == void.class || c2 == void.class) { + return Object.class; } else if(isConvertibleWithoutLoss(c2, c1)) { return c1; } else if(isConvertibleWithoutLoss(c1, c2)) { return c2; - } - if(c1 == void.class) { - return c2; - } else if(c2 == void.class) { - return c1; - } - if(c1.isPrimitive() && c2.isPrimitive()) { + } else if(c1.isPrimitive() && c2.isPrimitive()) { if((c1 == byte.class && c2 == char.class) || (c1 == char.class && c2 == byte.class)) { // byte + char = int return int.class; @@ -268,20 +264,24 @@ public class TypeUtilities { } /** - * Determines whether a type can be converted to another without losing any - * precision. + * Determines whether a type can be converted to another without losing any precision. As a special case, + * void is considered convertible only to Object and void, while anything can be converted to void. This + * is because a target type of void means we don't care about the value, so the conversion is always + * permissible. * * @param sourceType the source type * @param targetType the target type * @return true if lossless conversion is possible */ public static boolean isConvertibleWithoutLoss(final Class<?> sourceType, final Class<?> targetType) { - if(targetType.isAssignableFrom(sourceType)) { + if(targetType.isAssignableFrom(sourceType) || targetType == void.class) { return true; } if(sourceType.isPrimitive()) { if(sourceType == void.class) { - return false; // Void can't be losslessly represented by any type + // Void should be losslessly representable by Object, either as null or as a custom value that + // can be set with DynamicLinkerFactory.setAutoConversionStrategy. + return targetType == Object.class; } if(targetType.isPrimitive()) { return isProperPrimitiveLosslessSubtype(sourceType, targetType); @@ -520,4 +520,13 @@ public class TypeUtilities { public static Class<?> getWrapperType(final Class<?> primitiveType) { return WRAPPER_TYPES.get(primitiveType); } + + /** + * Returns true if the passed type is a wrapper for a primitive type. + * @param type the examined type + * @return true if the passed type is a wrapper for a primitive type. + */ + public static boolean isWrapperType(final Class<?> type) { + return PRIMITIVE_TYPES.containsKey(type); + } } diff --git a/src/jdk/nashorn/internal/AssertsEnabled.java b/src/jdk/nashorn/internal/AssertsEnabled.java index f0678664..73d9dfdc 100644 --- a/src/jdk/nashorn/internal/AssertsEnabled.java +++ b/src/jdk/nashorn/internal/AssertsEnabled.java @@ -27,8 +27,8 @@ package jdk.nashorn.internal; /** * Class that exposes the current state of asserts. - * */ +@SuppressWarnings("all") public final class AssertsEnabled { private static boolean assertsEnabled = false; static { diff --git a/src/jdk/nashorn/internal/codegen/ApplySpecialization.java b/src/jdk/nashorn/internal/codegen/ApplySpecialization.java index cdfe34f2..62e58f46 100644 --- a/src/jdk/nashorn/internal/codegen/ApplySpecialization.java +++ b/src/jdk/nashorn/internal/codegen/ApplySpecialization.java @@ -29,6 +29,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR; import static jdk.nashorn.internal.codegen.CompilerConstants.EXPLODED_ARGUMENT_PREFIX; import java.lang.invoke.MethodType; +import java.net.URL; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -93,6 +94,8 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple private final Deque<List<IdentNode>> explodedArguments = new ArrayDeque<>(); + private final Deque<MethodType> callSiteTypes = new ArrayDeque<>(); + private static final String ARGUMENTS = ARGUMENTS_VAR.symbolName(); /** @@ -118,86 +121,113 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple return context.getLogger(this.getClass()); } + @SuppressWarnings("serial") + private static class TransformFailedException extends RuntimeException { + TransformFailedException(final FunctionNode fn, final String message) { + super(massageURL(fn.getSource().getURL()) + '.' + fn.getName() + " => " + message, null, false, false); + } + } + + @SuppressWarnings("serial") + private static class AppliesFoundException extends RuntimeException { + AppliesFoundException() { + super("applies_found", null, false, false); + } + } + + private static final AppliesFoundException HAS_APPLIES = new AppliesFoundException(); + + private boolean hasApplies(final FunctionNode functionNode) { + try { + functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { + @Override + public boolean enterFunctionNode(final FunctionNode fn) { + return fn == functionNode; + } + + @Override + public boolean enterCallNode(final CallNode callNode) { + if (isApply(callNode)) { + throw HAS_APPLIES; + } + return true; + } + }); + } catch (final AppliesFoundException e) { + return true; + } + + log.fine("There are no applies in ", DebugLogger.quote(functionNode.getName()), " - nothing to do."); + return false; // no applies + } + /** * Arguments may only be used as args to the apply. Everything else is disqualified * We cannot control arguments if they escape from the method and go into an unknown * scope, thus we are conservative and treat any access to arguments outside the * apply call as a case of "we cannot apply the optimization". - * - * @return true if arguments escape */ - private boolean argumentsEscape(final FunctionNode functionNode) { - - @SuppressWarnings("serial") - final UnsupportedOperationException uoe = new UnsupportedOperationException() { - @Override - public synchronized Throwable fillInStackTrace() { - return null; - } - }; + private static void checkValidTransform(final FunctionNode functionNode) { final Set<Expression> argumentsFound = new HashSet<>(); final Deque<Set<Expression>> stack = new ArrayDeque<>(); + //ensure that arguments is only passed as arg to apply - try { - functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { - private boolean isCurrentArg(final Expression expr) { - return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call - } + functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { + + private boolean isCurrentArg(final Expression expr) { + return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call + } + + private boolean isArguments(final Expression expr) { + if (expr instanceof IdentNode && ARGUMENTS.equals(((IdentNode)expr).getName())) { + argumentsFound.add(expr); + return true; + } + return false; + } - private boolean isArguments(final Expression expr) { - if (expr instanceof IdentNode && ARGUMENTS.equals(((IdentNode)expr).getName())) { - argumentsFound.add(expr); + private boolean isParam(final String name) { + for (final IdentNode param : functionNode.getParameters()) { + if (param.getName().equals(name)) { return true; } - return false; } + return false; + } - private boolean isParam(final String name) { - for (final IdentNode param : functionNode.getParameters()) { - if (param.getName().equals(name)) { - return true; - } - } - return false; + @Override + public Node leaveIdentNode(final IdentNode identNode) { + if (isParam(identNode.getName())) { + throw new TransformFailedException(lc.getCurrentFunction(), "parameter: " + identNode.getName()); } - - @Override - public Node leaveIdentNode(final IdentNode identNode) { - if (isParam(identNode.getName()) || isArguments(identNode) && !isCurrentArg(identNode)) { - throw uoe; //avoid filling in stack trace - } - return identNode; + // it's OK if 'argument' occurs as the current argument of an apply + if (isArguments(identNode) && !isCurrentArg(identNode)) { + throw new TransformFailedException(lc.getCurrentFunction(), "is 'arguments': " + identNode.getName()); } + return identNode; + } - @Override - public boolean enterCallNode(final CallNode callNode) { - final Set<Expression> callArgs = new HashSet<>(); - if (isApply(callNode)) { - final List<Expression> argList = callNode.getArgs(); - if (argList.size() != 2 || !isArguments(argList.get(argList.size() - 1))) { - throw new UnsupportedOperationException(); - } - callArgs.addAll(callNode.getArgs()); + @Override + public boolean enterCallNode(final CallNode callNode) { + final Set<Expression> callArgs = new HashSet<>(); + if (isApply(callNode)) { + final List<Expression> argList = callNode.getArgs(); + if (argList.size() != 2 || !isArguments(argList.get(argList.size() - 1))) { + throw new TransformFailedException(lc.getCurrentFunction(), "argument pattern not matched: " + argList); } - stack.push(callArgs); - return true; + callArgs.addAll(callNode.getArgs()); } - - @Override - public Node leaveCallNode(final CallNode callNode) { - stack.pop(); - return callNode; - } - }); - } catch (final UnsupportedOperationException e) { - if (!argumentsFound.isEmpty()) { - log.fine("'arguments' is used but escapes, or is reassigned in '" + functionNode.getName() + "'. Aborting"); + stack.push(callArgs); + return true; } - return true; //bad - } - return false; + @Override + public Node leaveCallNode(final CallNode callNode) { + stack.pop(); + return callNode; + } + }); } @Override @@ -224,12 +254,14 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); - log.fine("Transformed ", - callNode, - " from apply to call => ", - newCallNode, - " in ", - DebugLogger.quote(lc.getCurrentFunction().getName())); + if (log.isEnabled()) { + log.fine("Transformed ", + callNode, + " from apply to call => ", + newCallNode, + " in ", + DebugLogger.quote(lc.getCurrentFunction().getName())); + } return newCallNode; } @@ -237,12 +269,12 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple return callNode; } - private boolean pushExplodedArgs(final FunctionNode functionNode) { + private void pushExplodedArgs(final FunctionNode functionNode) { int start = 0; final MethodType actualCallSiteType = compiler.getCallSiteType(functionNode); if (actualCallSiteType == null) { - return false; + throw new TransformFailedException(lc.getCurrentFunction(), "No callsite type"); } assert actualCallSiteType.parameterType(actualCallSiteType.parameterCount() - 1) != Object[].class : "error vararg callsite passed to apply2call " + functionNode.getName() + " " + actualCallSiteType; @@ -264,8 +296,8 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple } } + callSiteTypes.push(actualCallSiteType); explodedArguments.push(newParams); - return true; } @Override @@ -288,11 +320,30 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple return false; } - if (argumentsEscape(functionNode)) { + if (!hasApplies(functionNode)) { + return false; + } + + if (log.isEnabled()) { + log.info("Trying to specialize apply to call in '", + functionNode.getName(), + "' params=", + functionNode.getParameters(), + " id=", + functionNode.getId(), + " source=", + massageURL(functionNode.getSource().getURL())); + } + + try { + checkValidTransform(functionNode); + pushExplodedArgs(functionNode); + } catch (final TransformFailedException e) { + log.info("Failure: ", e.getMessage()); return false; } - return pushExplodedArgs(functionNode); + return true; } /** @@ -300,8 +351,8 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple * @return true if successful, false otherwise */ @Override - public Node leaveFunctionNode(final FunctionNode functionNode0) { - FunctionNode newFunctionNode = functionNode0; + public Node leaveFunctionNode(final FunctionNode functionNode) { + FunctionNode newFunctionNode = functionNode; final String functionName = newFunctionNode.getName(); if (changed.contains(newFunctionNode.getId())) { @@ -310,17 +361,18 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple setParameters(lc, explodedArguments.peek()); if (log.isEnabled()) { - log.info("Successfully specialized apply to call in '", + log.info("Success: ", + massageURL(newFunctionNode.getSource().getURL()), + '.', functionName, - " params=", - explodedArguments.peek(), "' id=", newFunctionNode.getId(), - " source=", - newFunctionNode.getSource().getURL()); + " params=", + callSiteTypes.peek()); } } + callSiteTypes.pop(); explodedArguments.pop(); return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED); @@ -331,4 +383,15 @@ public final class ApplySpecialization extends NodeVisitor<LexicalContext> imple return f instanceof AccessNode && "apply".equals(((AccessNode)f).getProperty()); } + private static String massageURL(final URL url) { + if (url == null) { + return "<null>"; + } + final String str = url.toString(); + final int slash = str.lastIndexOf('/'); + if (slash == -1) { + return str; + } + return str.substring(slash + 1); + } } diff --git a/src/jdk/nashorn/internal/codegen/AssignSymbols.java b/src/jdk/nashorn/internal/codegen/AssignSymbols.java index 88fd89bb..a4fb7bd1 100644 --- a/src/jdk/nashorn/internal/codegen/AssignSymbols.java +++ b/src/jdk/nashorn/internal/codegen/AssignSymbols.java @@ -189,7 +189,7 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl * @param body the body of the FunctionNode we are entering */ private void acceptDeclarations(final FunctionNode functionNode, final Block body) { - // This visitor will assign symbol to all declared variables, except "var" declarations in for loop initializers. + // This visitor will assign symbol to all declared variables. body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { @Override protected boolean enterDefault(final Node node) { @@ -200,16 +200,17 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl @Override public Node leaveVarNode(final VarNode varNode) { - if (varNode.isStatement()) { - final IdentNode ident = varNode.getName(); - final Block block = varNode.isBlockScoped() ? getLexicalContext().getCurrentBlock() : body; - final Symbol symbol = defineSymbol(block, ident.getName(), ident, varNode.getSymbolFlags()); - if (varNode.isFunctionDeclaration()) { - symbol.setIsFunctionDeclaration(); - } - return varNode.setName(ident.setSymbol(symbol)); + final IdentNode ident = varNode.getName(); + final boolean blockScoped = varNode.isBlockScoped(); + if (blockScoped && lc.inUnprotectedSwitchContext()) { + throwUnprotectedSwitchError(varNode); + } + final Block block = blockScoped ? lc.getCurrentBlock() : body; + final Symbol symbol = defineSymbol(block, ident.getName(), ident, varNode.getSymbolFlags()); + if (varNode.isFunctionDeclaration()) { + symbol.setIsFunctionDeclaration(); } - return varNode; + return varNode.setName(ident.setSymbol(symbol)); } }); } @@ -356,6 +357,10 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl throwParserException(ECMAErrors.getMessage("syntax.error.redeclare.variable", name), origin); } else { symbol.setHasBeenDeclared(); + // Set scope flag on top-level block scoped symbols + if (function.isProgram() && function.getBody() == block) { + symbol.setIsScope(); + } } } else if ((flags & IS_INTERNAL) != 0) { // Always create a new definition. @@ -540,7 +545,7 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl final int flags; if (varNode.isAnonymousFunctionDeclaration()) { flags = IS_INTERNAL; - } else if (lc.getCurrentFunction().isProgram()) { + } else if (!varNode.isBlockScoped() && lc.getCurrentFunction().isProgram()) { flags = IS_SCOPE; } else { flags = 0; @@ -905,7 +910,7 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl @Override public Node leaveSwitchNode(final SwitchNode switchNode) { // We only need a symbol for the tag if it's not an integer switch node - if(!switchNode.isInteger()) { + if(!switchNode.isUniqueInteger()) { switchNode.setTag(newObjectInternal(SWITCH_TAG_PREFIX)); } return switchNode; @@ -1044,6 +1049,15 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl return !(units == null || units.isEmpty()); } + private void throwUnprotectedSwitchError(final VarNode varNode) { + // Block scoped declarations in switch statements without explicit blocks should be declared + // in a common block that contains all the case clauses. We cannot support this without a + // fundamental rewrite of how switch statements are handled (case nodes contain blocks and are + // directly contained by switch node). As a temporary solution we throw a reference error here. + final String msg = ECMAErrors.getMessage("syntax.error.unprotected.switch.declaration", varNode.isLet() ? "let" : "const"); + throwParserException(msg, varNode); + } + private void throwParserException(final String message, final Node origin) { if (origin == null) { throw new ParserException(message); diff --git a/src/jdk/nashorn/internal/codegen/AstSerializer.java b/src/jdk/nashorn/internal/codegen/AstSerializer.java index 19197a26..dc35f964 100644 --- a/src/jdk/nashorn/internal/codegen/AstSerializer.java +++ b/src/jdk/nashorn/internal/codegen/AstSerializer.java @@ -48,11 +48,13 @@ final class AstSerializer { private static final int COMPRESSION_LEVEL = Options.getIntProperty("nashorn.serialize.compression", 4); static byte[] serialize(final FunctionNode fn) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (final ObjectOutputStream oout = new ObjectOutputStream(new DeflaterOutputStream(out, - new Deflater(COMPRESSION_LEVEL)))) { + final Deflater deflater = new Deflater(COMPRESSION_LEVEL); + try (final ObjectOutputStream oout = new ObjectOutputStream(new DeflaterOutputStream(out, deflater))) { oout.writeObject(removeInnerFunctionBodies(fn)); } catch (final IOException e) { throw new AssertionError("Unexpected exception serializing function", e); + } finally { + deflater.end(); } return out.toByteArray(); } diff --git a/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/src/jdk/nashorn/internal/codegen/CodeGenerator.java index d6743631..59aa258e 100644 --- a/src/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -208,6 +208,8 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex private static final Type ITERATOR_TYPE = Type.typeFor(ITERATOR_CLASS); private static final Type EXCEPTION_TYPE = Type.typeFor(CompilerConstants.EXCEPTION_PREFIX.type()); + private static final Integer INT_ZERO = Integer.valueOf(0); + /** Constant data & installation. The only reason the compiler keeps this is because it is assigned * by reflection in class installation */ private final Compiler compiler; @@ -463,10 +465,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex // If this is either __FILE__, __DIR__, or __LINE__ then load the property initially as Object as we'd convert // it anyway for replaceLocationPropertyPlaceholder. if(identNode.isCompileTimePropertyName()) { - method.dynamicGet(Type.OBJECT, identNode.getSymbol().getName(), flags, identNode.isFunction()); + method.dynamicGet(Type.OBJECT, identNode.getSymbol().getName(), flags, identNode.isFunction(), false); replaceCompileTimeProperty(); } else { - dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction()); + dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction(), false); } } } @@ -484,7 +486,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex private MethodEmitter storeFastScopeVar(final Symbol symbol, final int flags) { loadFastScopeProto(symbol, true); - method.dynamicSet(symbol.getName(), flags | CALLSITE_FAST_SCOPE); + method.dynamicSet(symbol.getName(), flags | CALLSITE_FAST_SCOPE, false); return method; } @@ -569,9 +571,11 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex // Operands' load type should not be narrower than the narrowest of the individual operand types, nor narrower // than the lower explicit bound, but it should also not be wider than - final Type narrowestOperandType = Type.narrowest(Type.widest(lhs.getType(), rhs.getType()), explicitOperandBounds.widest); + final Type lhsType = undefinedToNumber(lhs.getType()); + final Type rhsType = undefinedToNumber(rhs.getType()); + final Type narrowestOperandType = Type.narrowest(Type.widest(lhsType, rhsType), explicitOperandBounds.widest); final TypeBounds operandBounds = explicitOperandBounds.notNarrowerThan(narrowestOperandType); - if (noToPrimitiveConversion(lhs.getType(), explicitOperandBounds.widest) || rhs.isLocal()) { + if (noToPrimitiveConversion(lhsType, explicitOperandBounds.widest) || rhs.isLocal()) { // Can reorder. We might still need to separate conversion, but at least we can do it with reordering if (forceConversionSeparation) { // Can reorder, but can't move conversion into the operand as the operation depends on operands @@ -592,10 +596,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex // Can't reorder. Load and convert separately. final TypeBounds safeConvertBounds = TypeBounds.UNBOUNDED.notNarrowerThan(narrowestOperandType); loadExpression(lhs, safeConvertBounds, baseAlreadyOnStack); - final Type lhsType = method.peekType(); + final Type lhsLoadedType = method.peekType(); loadExpression(rhs, safeConvertBounds, false); final Type convertedLhsType = operandBounds.within(method.peekType()); - if (convertedLhsType != lhsType) { + if (convertedLhsType != lhsLoadedType) { // Do it conditionally, so that if conversion is a no-op we don't introduce a SWAP, SWAP. method.swap().convert(convertedLhsType).swap(); } @@ -607,13 +611,16 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex return method; } + private static final Type undefinedToNumber(final Type type) { + return type == Type.UNDEFINED ? Type.NUMBER : type; + } + private static final class TypeBounds { final Type narrowest; final Type widest; static final TypeBounds UNBOUNDED = new TypeBounds(Type.UNKNOWN, Type.OBJECT); static final TypeBounds INT = exact(Type.INT); - static final TypeBounds NUMBER = exact(Type.NUMBER); static final TypeBounds OBJECT = exact(Type.OBJECT); static final TypeBounds BOOLEAN = exact(Type.BOOLEAN); @@ -738,7 +745,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex @Override void consumeStack() { final int flags = getCallSiteFlags(); - dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction()); + dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction(), accessNode.isIndex()); } }.emit(baseAlreadyOnStack ? 1 : 0); return false; @@ -1442,7 +1449,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex // NOTE: not using a nested OptimisticOperation on this dynamicGet, as we expect to get back // a callable object. Nobody in their right mind would optimistically type this call site. assert !node.isOptimistic(); - method.dynamicGet(node.getType(), node.getProperty(), flags, true); + method.dynamicGet(node.getType(), node.getProperty(), flags, true, node.isIndex()); method.swap(); argCount = loadArgs(args); } @@ -2014,6 +2021,19 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex final Expression test = ifNode.getTest(); final Block pass = ifNode.getPass(); final Block fail = ifNode.getFail(); + + if (Expression.isAlwaysTrue(test)) { + loadAndDiscard(test); + pass.accept(this); + return false; + } else if (Expression.isAlwaysFalse(test)) { + loadAndDiscard(test); + if (fail != null) { + fail.accept(this); + } + return false; + } + final boolean hasFailConversion = LocalVariableConversion.hasLiveConversion(ifNode); final Label failLabel = new Label("if_fail"); @@ -2033,7 +2053,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex method.beforeJoinPoint(ifNode); } - if(afterLabel != null) { + if(afterLabel != null && afterLabel.isReachable()) { method.label(afterLabel); } @@ -2810,7 +2830,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex Label defaultLabel = defaultCase != null ? defaultCase.getEntry() : breakLabel; final boolean hasSkipConversion = LocalVariableConversion.hasLiveConversion(switchNode); - if (switchNode.isInteger()) { + if (switchNode.isUniqueInteger()) { // Tree for sorting values. final TreeMap<Integer, Label> tree = new TreeMap<>(); @@ -3144,14 +3164,13 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex if (isFastScope(identSymbol)) { storeFastScopeVar(identSymbol, flags); } else { - method.dynamicSet(identNode.getName(), flags); + method.dynamicSet(identNode.getName(), flags, false); } } else { final Type identType = identNode.getType(); if(identType == Type.UNDEFINED) { - // The symbol must not be slotted; the initializer is either itself undefined (explicit assignment of - // undefined to undefined), or the left hand side is a dead variable. - assert !identNode.getSymbol().isScope(); + // The initializer is either itself undefined (explicit assignment of undefined to undefined), + // or the left hand side is a dead variable. assert init.getType() == Type.UNDEFINED || identNode.getSymbol().slotCount() == 0; loadAndDiscard(init); return false; @@ -3263,6 +3282,13 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex emitContinueLabel(continueLabel, liveLocalsOnContinue); } + if (loopNode.hasPerIterationScope() && lc.getCurrentBlock().needsScope()) { + // ES6 for loops with LET init need a new scope for each iteration. We just create a shallow copy here. + method.loadCompilerConstant(SCOPE); + method.invoke(virtualCallNoLookup(ScriptObject.class, "copy", ScriptObject.class)); + method.storeCompilerConstant(SCOPE); + } + if(method.isReachable()) { if(modify != null) { lineNumber(loopNode); @@ -3567,8 +3593,9 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex operandBounds = new TypeBounds(binaryNode.getType(), Type.OBJECT); } else { // Non-optimistic, non-FP +. Allow it to overflow. - operandBounds = new TypeBounds(binaryNode.getWidestOperandType(), Type.OBJECT); - forceConversionSeparation = binaryNode.getWidestOperationType().narrowerThan(resultBounds.widest); + final Type widestOperationType = binaryNode.getWidestOperationType(); + operandBounds = new TypeBounds(Type.narrowest(binaryNode.getWidestOperandType(), resultBounds.widest), widestOperationType); + forceConversionSeparation = widestOperationType.narrowerThan(resultBounds.widest); } loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), operandBounds, false, forceConversionSeparation); } @@ -3683,8 +3710,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex final Expression lhs = assignNode.lhs(); final Expression rhs = assignNode.rhs(); final Type widestOperationType = assignNode.getWidestOperationType(); - final Type widest = assignNode.isTokenType(TokenType.ASSIGN_ADD) ? Type.OBJECT : widestOperationType; - final TypeBounds bounds = new TypeBounds(assignNode.getType(), widest); + final TypeBounds bounds = new TypeBounds(assignNode.getType(), widestOperationType); new OptimisticOperation(assignNode, bounds) { @Override void loadStack() { @@ -3817,7 +3843,12 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex private void doSHR() { // TODO: make SHR optimistic - method.shr().convert(Type.LONG).load(JSType.MAX_UINT).and(); + method.shr(); + toUint(); + } + + private void toUint() { + JSType.TO_UINT32_I.invoke(method); } private void loadASSIGN_SUB(final BinaryNode binaryNode) { @@ -3849,12 +3880,8 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex operandBounds = numericBounds; } else { final boolean isOptimistic = isValid(getProgramPoint()); - if(isOptimistic) { + if(isOptimistic || node.isTokenType(TokenType.DIV) || node.isTokenType(TokenType.MOD)) { operandBounds = new TypeBounds(node.getType(), Type.NUMBER); - } else if(node.isTokenType(TokenType.DIV) || node.isTokenType(TokenType.MOD)) { - // Non-optimistic division must always take double arguments as its result must also be - // double. - operandBounds = TypeBounds.NUMBER; } else { // Non-optimistic, non-FP subtraction or multiplication. Allow them to overflow. operandBounds = new TypeBounds(Type.narrowest(node.getWidestOperandType(), @@ -3879,8 +3906,18 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex } private void loadBIT_OR(final BinaryNode binaryNode) { - loadBinaryOperands(binaryNode); - method.or(); + // Optimize x|0 to (int)x + if (isRhsZero(binaryNode)) { + loadExpressionAsType(binaryNode.lhs(), Type.INT); + } else { + loadBinaryOperands(binaryNode); + method.or(); + } + } + + private static boolean isRhsZero(final BinaryNode binaryNode) { + final Expression rhs = binaryNode.rhs(); + return rhs instanceof LiteralNode && INT_ZERO.equals(((LiteralNode<?>)rhs).getValue()); } private void loadBIT_XOR(final BinaryNode binaryNode) { @@ -3957,8 +3994,14 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex } private void loadSHR(final BinaryNode binaryNode) { - loadBinaryOperands(binaryNode); - doSHR(); + // Optimize x >>> 0 to (uint)x + if (isRhsZero(binaryNode)) { + loadExpressionAsType(binaryNode.lhs(), Type.INT); + toUint(); + } else { + loadBinaryOperands(binaryNode); + doSHR(); + } } private void loadSUB(final BinaryNode binaryNode, final TypeBounds resultBounds) { @@ -4225,7 +4268,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex if (isFastScope(symbol)) { storeFastScopeVar(symbol, flags); } else { - method.dynamicSet(node.getName(), flags); + method.dynamicSet(node.getName(), flags, false); } } else { final Type storeType = assignNode.getType(); @@ -4242,7 +4285,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex @Override public boolean enterAccessNode(final AccessNode node) { - method.dynamicSet(node.getProperty(), getCallSiteFlags()); + method.dynamicSet(node.getProperty(), getCallSiteFlags(), node.isIndex()); return false; } @@ -4580,11 +4623,11 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex * @param isMethod whether we're preferrably retrieving a function * @return the current method emitter */ - MethodEmitter dynamicGet(final String name, final int flags, final boolean isMethod) { + MethodEmitter dynamicGet(final String name, final int flags, final boolean isMethod, final boolean isIndex) { if(isOptimistic) { - return method.dynamicGet(getOptimisticCoercedType(), name, getOptimisticFlags(flags), isMethod); + return method.dynamicGet(getOptimisticCoercedType(), name, getOptimisticFlags(flags), isMethod, isIndex); } - return method.dynamicGet(resultBounds.within(expression.getType()), name, nonOptimisticFlags(flags), isMethod); + return method.dynamicGet(resultBounds.within(expression.getType()), name, nonOptimisticFlags(flags), isMethod, isIndex); } MethodEmitter dynamicGetIndex(final int flags, final boolean isMethod) { diff --git a/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java b/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java index 08c4b65c..87a0802c 100644 --- a/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java +++ b/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.Map; - import jdk.nashorn.internal.IntDeque; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.Block; @@ -250,7 +249,7 @@ final class CodeGeneratorLexicalContext extends LexicalContext { static Type getTypeForSlotDescriptor(final char typeDesc) { // Recognizing both lowercase and uppercase as we're using both to signify symbol boundaries; see // MethodEmitter.markSymbolBoundariesInLvarTypesDescriptor(). - switch(typeDesc) { + switch (typeDesc) { case 'I': case 'i': return Type.INT; diff --git a/src/jdk/nashorn/internal/codegen/Compiler.java b/src/jdk/nashorn/internal/codegen/Compiler.java index 196862f0..740022b2 100644 --- a/src/jdk/nashorn/internal/codegen/Compiler.java +++ b/src/jdk/nashorn/internal/codegen/Compiler.java @@ -389,6 +389,7 @@ public final class Compiler implements Loggable { * @param continuationEntryPoints continuation entry points for restof method * @param runtimeScope runtime scope for recompilation type lookup in {@code TypeEvaluator} */ + @SuppressWarnings("unused") public Compiler( final Context context, final ScriptEnvironment env, diff --git a/src/jdk/nashorn/internal/codegen/FoldConstants.java b/src/jdk/nashorn/internal/codegen/FoldConstants.java index fe81b2b4..092b788f 100644 --- a/src/jdk/nashorn/internal/codegen/FoldConstants.java +++ b/src/jdk/nashorn/internal/codegen/FoldConstants.java @@ -26,12 +26,16 @@ package jdk.nashorn.internal.codegen; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.BlockStatement; +import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.EmptyNode; +import jdk.nashorn.internal.ir.Expression; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.IfNode; @@ -40,6 +44,7 @@ import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.Statement; +import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.VarNode; @@ -126,11 +131,37 @@ final class FoldConstants extends NodeVisitor<LexicalContext> implements Loggabl public Node leaveTernaryNode(final TernaryNode ternaryNode) { final Node test = ternaryNode.getTest(); if (test instanceof LiteralNode.PrimitiveLiteralNode) { - return ((LiteralNode.PrimitiveLiteralNode<?>)test).isTrue() ? ternaryNode.getTrueExpression() : ternaryNode.getFalseExpression(); + return (((LiteralNode.PrimitiveLiteralNode<?>)test).isTrue() ? ternaryNode.getTrueExpression() : ternaryNode.getFalseExpression()).getExpression(); } return ternaryNode; } + @Override + public Node leaveSwitchNode(final SwitchNode switchNode) { + return switchNode.setUniqueInteger(lc, isUniqueIntegerSwitchNode(switchNode)); + } + + private static boolean isUniqueIntegerSwitchNode(final SwitchNode switchNode) { + final Set<Integer> alreadySeen = new HashSet<>(); + for (final CaseNode caseNode : switchNode.getCases()) { + final Expression test = caseNode.getTest(); + if (test != null && !isUniqueIntegerLiteral(test, alreadySeen)) { + return false; + } + } + return true; + } + + private static boolean isUniqueIntegerLiteral(final Expression expr, final Set<Integer> alreadySeen) { + if (expr instanceof LiteralNode) { + final Object value = ((LiteralNode<?>)expr).getValue(); + if (value instanceof Integer) { + return alreadySeen.add((Integer)value); + } + } + return false; + } + /** * Helper class to evaluate constant expressions at compile time This is * also a simplifier used by BinaryNode visits, UnaryNode visits and @@ -291,7 +322,7 @@ final class FoldConstants extends NodeVisitor<LexicalContext> implements Loggabl value = lhs.getNumber() - rhs.getNumber(); break; case SHR: - return LiteralNode.newInstance(token, finish, (lhs.getInt32() >>> rhs.getInt32()) & JSType.MAX_UINT); + return LiteralNode.newInstance(token, finish, JSType.toUint32(lhs.getInt32() >>> rhs.getInt32())); case SAR: return LiteralNode.newInstance(token, finish, lhs.getInt32() >> rhs.getInt32()); case SHL: diff --git a/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java b/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java index ea073623..ac3c2934 100644 --- a/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java +++ b/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java @@ -83,7 +83,6 @@ import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.VarNode; import jdk.nashorn.internal.ir.WhileNode; import jdk.nashorn.internal.ir.visitor.NodeVisitor; -import jdk.nashorn.internal.parser.Token; import jdk.nashorn.internal.parser.TokenType; /** @@ -94,6 +93,13 @@ import jdk.nashorn.internal.parser.TokenType; * variable to its widest used type after the join point. That would eliminate some widenings of undefined variables to * object, most notably those used only in loops. We need a full liveness analysis for that. Currently, we can establish * per-type liveness, which eliminates most of unwanted dead widenings. + * NOTE: the way this class is implemented, it actually processes the AST in two passes. The first pass is top-down and + * implemented in {@code enterXxx} methods. This pass does not mutate the AST (except for one occurrence, noted below), + * as being able to find relevant labels for control flow joins is sensitive to their reference identity, and mutated + * label-carrying nodes will create copies of their labels. A second bottom-up pass applying the changes is implemented + * in the separate visitor sitting in {@link #leaveFunctionNode(FunctionNode)}. This visitor will also instantiate new + * instances of the calculator to be run on nested functions (when not lazy compiling). + * */ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ @@ -236,12 +242,12 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ private byte conversions; void recordConversion(final LvarType from, final LvarType to) { - switch(from) { + switch (from) { case UNDEFINED: return; case INT: case BOOLEAN: - switch(to) { + switch (to) { case LONG: recordConversion(I2L); return; @@ -256,7 +262,7 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ return; } case LONG: - switch(to) { + switch (to) { case DOUBLE: recordConversion(L2D); return; @@ -399,48 +405,53 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ @Override public boolean enterBinaryNode(final BinaryNode binaryNode) { + // NOTE: regardless of operator's lexical associativity, lhs is always evaluated first. final Expression lhs = binaryNode.lhs(); - final Expression rhs = binaryNode.rhs(); final boolean isAssignment = binaryNode.isAssignment(); - - final TokenType tokenType = Token.descType(binaryNode.getToken()); - if(tokenType.isLeftAssociative()) { - assert !isAssignment; - final boolean isLogical = binaryNode.isLogical(); - final Label joinLabel = isLogical ? new Label("") : null; - lhs.accept(this); - if(isLogical) { - jumpToLabel((JoinPredecessor)lhs, joinLabel); - } - rhs.accept(this); - if(isLogical) { - jumpToLabel((JoinPredecessor)rhs, joinLabel); - } - joinOnLabel(joinLabel); - } else { - rhs.accept(this); - if(isAssignment) { - if(lhs instanceof BaseNode) { - ((BaseNode)lhs).getBase().accept(this); - if(lhs instanceof IndexNode) { - ((IndexNode)lhs).getIndex().accept(this); - } else { - assert lhs instanceof AccessNode; - } + LvarType lhsTypeOnLoad = null; + if(isAssignment) { + if(lhs instanceof BaseNode) { + ((BaseNode)lhs).getBase().accept(this); + if(lhs instanceof IndexNode) { + ((IndexNode)lhs).getIndex().accept(this); } else { - assert lhs instanceof IdentNode; - if(binaryNode.isSelfModifying()) { - ((IdentNode)lhs).accept(this); - } + assert lhs instanceof AccessNode; } } else { - lhs.accept(this); + assert lhs instanceof IdentNode; + if(binaryNode.isSelfModifying()) { + final IdentNode ident = ((IdentNode)lhs); + ident.accept(this); + // Self-assignment can cause a change in the type of the variable. For purposes of evaluating + // the type of the operation, we must use its type as it was when it was loaded. If we didn't + // do this, some awkward expressions would end up being calculated incorrectly, e.g. + // "var x; x += x = 0;". In this case we have undefined+int so the result type is double (NaN). + // However, if we used the type of "x" on LHS after we evaluated RHS, we'd see int+int, so the + // result type would be either optimistic int or pessimistic long, which would be wrong. + lhsTypeOnLoad = getLocalVariableTypeIfBytecode(ident.getSymbol()); + } } + } else { + lhs.accept(this); + } + + final boolean isLogical = binaryNode.isLogical(); + assert !(isAssignment && isLogical); // there are no logical assignment operators in JS + final Label joinLabel = isLogical ? new Label("") : null; + if(isLogical) { + jumpToLabel((JoinPredecessor)lhs, joinLabel); + } + + final Expression rhs = binaryNode.rhs(); + rhs.accept(this); + if(isLogical) { + jumpToLabel((JoinPredecessor)rhs, joinLabel); } + joinOnLabel(joinLabel); if(isAssignment && lhs instanceof IdentNode) { if(binaryNode.isSelfModifying()) { - onSelfAssignment((IdentNode)lhs, binaryNode); + onSelfAssignment((IdentNode)lhs, binaryNode, lhsTypeOnLoad); } else { onAssignment((IdentNode)lhs, rhs); } @@ -705,7 +716,7 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ // Control flow is different for all-integer cases where we dispatch by switch table, and for all other cases // where we do sequential comparison. Note that CaseNode objects act as join points. - final boolean isInteger = switchNode.isInteger(); + final boolean isInteger = switchNode.isUniqueInteger(); final Label breakLabel = switchNode.getBreakLabel(); final boolean hasDefault = switchNode.getDefaultCase() != null; @@ -920,7 +931,8 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ if(unaryNode.isSelfModifying()) { if(expr instanceof IdentNode) { - onSelfAssignment((IdentNode)expr, unaryNode); + final IdentNode ident = (IdentNode)expr; + onSelfAssignment(ident, unaryNode, getLocalVariableTypeIfBytecode(ident.getSymbol())); } } return false; @@ -974,12 +986,41 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ return types; } + /** + * Returns the current type of the local variable represented by the symbol. This is the most strict of all + * {@code getLocalVariableType*} methods, as it will throw an assertion if the type is null. Therefore, it is only + * safe to be invoked on symbols known to be bytecode locals, and only after they have been initialized. + * Regardless, it is recommended to use this method in majority of cases, as because of its strictness it is the + * best suited for catching missing type calculation bugs early. + * @param symbol a symbol representing a bytecode local variable. + * @return the current type of the local variable represented by the symbol + */ private LvarType getLocalVariableType(final Symbol symbol) { final LvarType type = getLocalVariableTypeOrNull(symbol); assert type != null; return type; } + /** + * Gets the type for a local variable if it is a bytecode local, otherwise null. Can be used in circumstances where + * the type is irrelevant if the symbol is not a bytecode local. Note that for bytecode locals, it delegates to + * {@link #getLocalVariableType(Symbol)}, so it will still assert that the type for such variable is already + * defined (that is, not null). + * @param symbol the symbol representing the variable. + * @return the current variable type, if it is a bytecode local, otherwise null. + */ + private LvarType getLocalVariableTypeIfBytecode(final Symbol symbol) { + return symbol.isBytecodeLocal() ? getLocalVariableType(symbol) : null; + } + + /** + * Gets the type for a variable represented by a symbol, or null if the type is not know. This is the least strict + * of all local variable type getters, and as such its use is discouraged except in initialization scenarios (where + * a just-defined symbol might still be null). + * @param symbol the symbol + * @return the current type for the symbol, or null if the type is not known either because the symbol has not been + * initialized, or because the symbol does not represent a bytecode local variable. + */ private LvarType getLocalVariableTypeOrNull(final Symbol symbol) { return localVariableTypes.get(symbol); } @@ -1359,13 +1400,13 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ jumpToCatchBlock(identNode); } - private void onSelfAssignment(final IdentNode identNode, final Expression assignment) { + private void onSelfAssignment(final IdentNode identNode, final Expression assignment, final LvarType typeOnLoad) { final Symbol symbol = identNode.getSymbol(); assert symbol != null : identNode.getName(); if(!symbol.isBytecodeLocal()) { return; } - final LvarType type = toLvarType(getType(assignment)); + final LvarType type = toLvarType(getType(assignment, symbol, typeOnLoad.type)); // Self-assignment never produce either a boolean or undefined assert type != null && type != LvarType.UNDEFINED && type != LvarType.BOOLEAN; setType(symbol, type); @@ -1425,6 +1466,7 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ * @param symbol the symbol representing the variable * @param type the type */ + @SuppressWarnings("unused") private void setType(final Symbol symbol, final LvarType type) { if(getLocalVariableTypeOrNull(symbol) == type) { return; @@ -1445,13 +1487,24 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ symbolIsUsed(symbol, getLocalVariableType(symbol)); } + /** + * Gets the type of the expression, dependent on the current types of the local variables. + * + * @param expr the expression + * @return the current type of the expression dependent on the current types of the local variables. + */ private Type getType(final Expression expr) { return expr.getType(getSymbolToType()); } + /** + * Returns a function object from symbols to their types, used by the expressions to evaluate their type. + * {@link BinaryNode} specifically uses identity of the function to cache type calculations. This method makes + * sure to return the same function object while the local variable types don't change, and create a new function + * object if the local variable types have been changed. + * @return a function object representing a mapping from symbols to their types. + */ private Function<Symbol, Type> getSymbolToType() { - // BinaryNode uses identity of the function to cache type calculations. Therefore, we must use different - // function instances for different localVariableTypes instances. if(symbolToType.isStale()) { symbolToType = new SymbolToType(); } @@ -1469,4 +1522,41 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{ return boundTypes != localVariableTypes; } } + + /** + * Gets the type of the expression, dependent on the current types of the local variables and a single overridden + * symbol type. Used by type calculation on compound operators to ensure the type of the LHS at the time it was + * loaded (which can potentially be different after RHS evaluation, e.g. "var x; x += x = 0;") is preserved for + * the calculation. + * + * @param expr the expression + * @param overriddenSymbol the overridden symbol + * @param overriddenType the overridden type + * @return the current type of the expression dependent on the current types of the local variables and the single + * potentially overridden type. + */ + private Type getType(final Expression expr, final Symbol overriddenSymbol, final Type overriddenType) { + return expr.getType(getSymbolToType(overriddenSymbol, overriddenType)); + } + + private Function<Symbol, Type> getSymbolToType(final Symbol overriddenSymbol, final Type overriddenType) { + return getLocalVariableType(overriddenSymbol).type == overriddenType ? getSymbolToType() : + new SymbolToTypeOverride(overriddenSymbol, overriddenType); + } + + private class SymbolToTypeOverride implements Function<Symbol, Type> { + private final Function<Symbol, Type> originalSymbolToType = getSymbolToType(); + private final Symbol overriddenSymbol; + private final Type overriddenType; + + SymbolToTypeOverride(final Symbol overriddenSymbol, final Type overriddenType) { + this.overriddenSymbol = overriddenSymbol; + this.overriddenType = overriddenType; + } + + @Override + public Type apply(final Symbol symbol) { + return symbol == overriddenSymbol ? overriddenType : originalSymbolToType.apply(symbol); + } + } } diff --git a/src/jdk/nashorn/internal/codegen/Lower.java b/src/jdk/nashorn/internal/codegen/Lower.java index 724c6f1e..a30e79a4 100644 --- a/src/jdk/nashorn/internal/codegen/Lower.java +++ b/src/jdk/nashorn/internal/codegen/Lower.java @@ -34,6 +34,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.ListIterator; +import java.util.regex.Pattern; +import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BaseNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; @@ -52,6 +54,7 @@ import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; +import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.JumpStatement; import jdk.nashorn.internal.ir.LabelNode; import jdk.nashorn.internal.ir.LexicalContext; @@ -93,6 +96,10 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo private final DebugLogger log; + // Conservative pattern to test if element names consist of characters valid for identifiers. + // This matches any non-zero length alphanumeric string including _ and $ and not starting with a digit. + private static Pattern SAFE_PROPERTY_NAME = Pattern.compile("[a-zA-Z_$][\\w$]*"); + /** * Constructor. */ @@ -140,7 +147,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo } }); - this.log = initLogger(compiler.getContext()); + this.log = initLogger(compiler.getContext()); } @Override @@ -181,6 +188,28 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo } @Override + public Node leaveIndexNode(final IndexNode indexNode) { + final String name = getConstantPropertyName(indexNode.getIndex()); + if (name != null) { + // If index node is a constant property name convert index node to access node. + assert Token.descType(indexNode.getToken()) == TokenType.LBRACKET; + return new AccessNode(indexNode.getToken(), indexNode.getFinish(), indexNode.getBase(), name); + } + return super.leaveIndexNode(indexNode); + } + + // If expression is a primitive literal that is not an array index and does return its string value. Else return null. + private static String getConstantPropertyName(final Expression expression) { + if (expression instanceof LiteralNode.PrimitiveLiteralNode) { + final Object value = ((LiteralNode) expression).getValue(); + if (value instanceof String && SAFE_PROPERTY_NAME.matcher((String) value).matches()) { + return (String) value; + } + } + return null; + } + + @Override public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) { final Expression expr = expressionStatement.getExpression(); ExpressionStatement node = expressionStatement; @@ -275,7 +304,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo @Override public Node leaveSwitchNode(final SwitchNode switchNode) { - if(!switchNode.isInteger()) { + if(!switchNode.isUniqueInteger()) { // Wrap it in a block so its internally created tag is restricted in scope addStatementEnclosedInBlock(switchNode); } else { @@ -525,7 +554,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo if (isAlwaysTrue(test)) { //turn it into a for node without a test. - final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, ForNode.IS_FOR).accept(this); + final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, 0).accept(this); lc.replace(whileNode, forNode); return forNode; } diff --git a/src/jdk/nashorn/internal/codegen/MapCreator.java b/src/jdk/nashorn/internal/codegen/MapCreator.java index 3ba3f630..1bec86fc 100644 --- a/src/jdk/nashorn/internal/codegen/MapCreator.java +++ b/src/jdk/nashorn/internal/codegen/MapCreator.java @@ -152,6 +152,10 @@ public class MapCreator<T> { flags |= Property.NOT_WRITABLE; } + if (symbol.isBlockScoped()) { + flags |= Property.IS_LEXICAL_BINDING; + } + // Mark symbol as needing declaration. Access before declaration will throw a ReferenceError. if (symbol.isBlockScoped() && symbol.isScope()) { flags |= Property.NEEDS_DECLARATION; diff --git a/src/jdk/nashorn/internal/codegen/MethodEmitter.java b/src/jdk/nashorn/internal/codegen/MethodEmitter.java index 87bb297f..362a7363 100644 --- a/src/jdk/nashorn/internal/codegen/MethodEmitter.java +++ b/src/jdk/nashorn/internal/codegen/MethodEmitter.java @@ -2214,10 +2214,10 @@ public class MethodEmitter implements Emitter { * @param name name of property * @param flags call site flags * @param isMethod should it prefer retrieving methods - * + * @param isIndex is this an index operation? * @return the method emitter */ - MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod) { + MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod, final boolean isIndex) { debug("dynamic_get", name, valueType, getProgramPoint(flags)); Type type = valueType; @@ -2226,8 +2226,8 @@ public class MethodEmitter implements Emitter { } popType(Type.SCOPE); - method.visitInvokeDynamicInsn((isMethod ? "dyn:getMethod|getProp|getElem:" : "dyn:getProp|getElem|getMethod:") + - NameCodec.encode(name), Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags); + method.visitInvokeDynamicInsn(dynGetOperation(isMethod, isIndex) + ':' + NameCodec.encode(name), + Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags); pushType(type); convert(valueType); //most probably a nop @@ -2240,8 +2240,9 @@ public class MethodEmitter implements Emitter { * * @param name name of property * @param flags call site flags + * @param isIndex is this an index operation? */ - void dynamicSet(final String name, final int flags) { + void dynamicSet(final String name, final int flags, final boolean isIndex) { assert !isOptimistic(flags); debug("dynamic_set", name, peekType()); @@ -2253,7 +2254,8 @@ public class MethodEmitter implements Emitter { popType(type); popType(Type.SCOPE); - method.visitInvokeDynamicInsn("dyn:setProp|setElem:" + NameCodec.encode(name), methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags); + method.visitInvokeDynamicInsn(dynSetOperation(isIndex) + ':' + NameCodec.encode(name), + methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags); } /** @@ -2286,7 +2288,7 @@ public class MethodEmitter implements Emitter { final String signature = Type.getMethodDescriptor(resultType, Type.OBJECT /*e.g STRING->OBJECT*/, index); - method.visitInvokeDynamicInsn(isMethod ? "dyn:getMethod|getElem|getProp" : "dyn:getElem|getProp|getMethod", signature, LINKERBOOTSTRAP, flags); + method.visitInvokeDynamicInsn(dynGetOperation(isMethod, true), signature, LINKERBOOTSTRAP, flags); pushType(resultType); if (result.isBoolean()) { @@ -2500,6 +2502,18 @@ public class MethodEmitter implements Emitter { } } + private static String dynGetOperation(final boolean isMethod, final boolean isIndex) { + if (isMethod) { + return isIndex ? "dyn:getMethod|getElem|getProp" : "dyn:getMethod|getProp|getElem"; + } else { + return isIndex ? "dyn:getElem|getProp|getMethod" : "dyn:getProp|getElem|getMethod"; + } + } + + private static String dynSetOperation(final boolean isIndex) { + return isIndex ? "dyn:setElem|setProp" : "dyn:setProp|setElem"; + } + private Type emitLocalVariableConversion(final LocalVariableConversion conversion, final boolean onlySymbolLiveValue) { final Type from = conversion.getFrom(); final Type to = conversion.getTo(); diff --git a/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java b/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java index 05a76872..53a107e6 100644 --- a/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java +++ b/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java @@ -33,6 +33,8 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -221,11 +223,37 @@ public final class OptimisticTypesPersistence { 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); + reportError(String.format("Failed to %s %s", msg, file), e); lastReportedError = now; } } + /** + * Logs an error message with warning severity (reasoning being that we're reporting an error that'll disable the + * type info cache, but it's only logged as a warning because that doesn't prevent Nashorn from running, it just + * disables a performance-enhancing cache). + * @param msg the message to log + * @param e the exception that represents the error. + */ + private static void reportError(final String msg, final Exception e) { + getLogger().warning(msg, "\n", exceptionToString(e)); + } + + /** + * A helper that prints an exception stack trace into a string. We have to do this as if we just pass the exception + * to {@link DebugLogger#warning(Object...)}, it will only log the exception message and not the stack, making + * problems harder to diagnose. + * @param e the exception + * @return the string representation of {@link Exception#printStackTrace()} output. + */ + private static String exceptionToString(final Exception e) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, false); + e.printStackTrace(pw); + pw.flush(); + return sw.toString(); + } + private static File createBaseCacheDir() { if(MAX_FILES == 0 || Options.getBooleanProperty("nashorn.typeInfo.disabled")) { return null; @@ -233,7 +261,7 @@ public final class OptimisticTypesPersistence { try { return createBaseCacheDirPrivileged(); } catch(final Exception e) { - getLogger().warning("Failed to create cache dir", e); + reportError("Failed to create cache dir", e); return null; } } @@ -267,7 +295,7 @@ public final class OptimisticTypesPersistence { try { return createCacheDirPrivileged(baseDir); } catch(final Exception e) { - getLogger().warning("Failed to create cache dir", e); + reportError("Failed to create cache dir", e); return null; } } @@ -280,7 +308,7 @@ public final class OptimisticTypesPersistence { try { versionDirName = getVersionDirName(); } catch(final Exception e) { - getLogger().warning("Failed to calculate version dir name", e); + reportError("Failed to calculate version dir name", e); return null; } final File versionDir = new File(baseDir, versionDirName); @@ -323,10 +351,17 @@ public final class OptimisticTypesPersistence { * 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 + * + * @return digest of currently running nashorn + * @throws Exception if digest could not be created */ - private static String getVersionDirName() throws Exception { - final URL url = OptimisticTypesPersistence.class.getResource(""); + public static String getVersionDirName() throws Exception { + // NOTE: getResource("") won't work if the JAR file doesn't have directory entries (and JAR files in JDK distro + // don't, or at least it's a bad idea counting on it). Alternatively, we could've tried + // getResource("OptimisticTypesPersistence.class") but behavior of getResource with regard to its willingness + // to hand out URLs to .class files is also unspecified. Therefore, the most robust way to obtain an URL to our + // package is to have a small non-class anchor file and start out from its URL. + final URL url = OptimisticTypesPersistence.class.getResource("anchor.properties"); final String protocol = url.getProtocol(); if (protocol.equals("jar")) { // Normal deployment: nashorn.jar diff --git a/src/jdk/nashorn/internal/codegen/SharedScopeCall.java b/src/jdk/nashorn/internal/codegen/SharedScopeCall.java index 56da4e00..078324cc 100644 --- a/src/jdk/nashorn/internal/codegen/SharedScopeCall.java +++ b/src/jdk/nashorn/internal/codegen/SharedScopeCall.java @@ -156,7 +156,7 @@ class SharedScopeCall { assert !isCall || valueType.isObject(); // Callables are always objects // If flags are optimistic, but we're doing a call, remove optimistic flags from the getter, as they obviously // only apply to the call. - method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall); + method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall, false); // If this is a get we're done, otherwise call the value as function. if (isCall) { diff --git a/src/jdk/nashorn/internal/codegen/SpillObjectCreator.java b/src/jdk/nashorn/internal/codegen/SpillObjectCreator.java index 4fa51091..40d12dfb 100644 --- a/src/jdk/nashorn/internal/codegen/SpillObjectCreator.java +++ b/src/jdk/nashorn/internal/codegen/SpillObjectCreator.java @@ -88,7 +88,7 @@ public final class SpillObjectCreator extends ObjectCreator<Expression> { final Property property = propertyMap.findProperty(key); if (property != null) { // normal property key - property.setCurrentType(JSType.unboxedFieldType(constantValue)); + property.setType(JSType.unboxedFieldType(constantValue)); final int slot = property.getSlot(); if (!OBJECT_FIELDS_ONLY && constantValue instanceof Number) { jpresetValues[slot] = ObjectClassGenerator.pack((Number)constantValue); diff --git a/src/jdk/nashorn/internal/codegen/TypeEvaluator.java b/src/jdk/nashorn/internal/codegen/TypeEvaluator.java index 4f3bc07f..d5282a8b 100644 --- a/src/jdk/nashorn/internal/codegen/TypeEvaluator.java +++ b/src/jdk/nashorn/internal/codegen/TypeEvaluator.java @@ -117,7 +117,7 @@ final class TypeEvaluator { } final Property property = find.getProperty(); - final Class<?> propertyClass = property.getCurrentType(); + final Class<?> propertyClass = property.getType(); if (propertyClass == null) { // propertyClass == null means its value is Undefined. It is probably not initialized yet, so we won't make // a type assumption yet. diff --git a/src/jdk/nashorn/internal/codegen/anchor.properties b/src/jdk/nashorn/internal/codegen/anchor.properties new file mode 100644 index 00000000..1a930682 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/anchor.properties @@ -0,0 +1,27 @@ +# +# 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. 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. +# + + +# This file exists only so OptimisticTypesPersistence.getVersionDirName() can take its URL. diff --git a/src/jdk/nashorn/internal/codegen/types/IntType.java b/src/jdk/nashorn/internal/codegen/types/IntType.java index dfa77467..43b06806 100644 --- a/src/jdk/nashorn/internal/codegen/types/IntType.java +++ b/src/jdk/nashorn/internal/codegen/types/IntType.java @@ -55,6 +55,7 @@ import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_ import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.nashorn.internal.codegen.CompilerConstants; +import jdk.nashorn.internal.runtime.JSType; /** * Type class: INT @@ -230,19 +231,21 @@ class IntType extends BitwiseType { @Override public Type div(final MethodVisitor method, final int programPoint) { - // Never perform non-optimistic integer division in JavaScript. - assert programPoint != INVALID_PROGRAM_POINT; - - method.visitInvokeDynamicInsn("idiv", "(II)I", MATHBOOTSTRAP, programPoint); + if (programPoint == INVALID_PROGRAM_POINT) { + JSType.DIV_ZERO.invoke(method); + } else { + method.visitInvokeDynamicInsn("idiv", "(II)I", MATHBOOTSTRAP, programPoint); + } return INT; } @Override public Type rem(final MethodVisitor method, final int programPoint) { - // Never perform non-optimistic integer remainder in JavaScript. - assert programPoint != INVALID_PROGRAM_POINT; - - method.visitInvokeDynamicInsn("irem", "(II)I", MATHBOOTSTRAP, programPoint); + if (programPoint == INVALID_PROGRAM_POINT) { + JSType.REM_ZERO.invoke(method); + } else { + method.visitInvokeDynamicInsn("irem", "(II)I", MATHBOOTSTRAP, programPoint); + } return INT; } diff --git a/src/jdk/nashorn/internal/codegen/types/LongType.java b/src/jdk/nashorn/internal/codegen/types/LongType.java index 7c3b2675..aa2ceb23 100644 --- a/src/jdk/nashorn/internal/codegen/types/LongType.java +++ b/src/jdk/nashorn/internal/codegen/types/LongType.java @@ -170,19 +170,21 @@ class LongType extends BitwiseType { @Override public Type div(final MethodVisitor method, final int programPoint) { - // Never perform non-optimistic integer division in JavaScript. - assert programPoint != INVALID_PROGRAM_POINT; - - method.visitInvokeDynamicInsn("ldiv", "(JJ)J", MATHBOOTSTRAP, programPoint); + if (programPoint == INVALID_PROGRAM_POINT) { + JSType.DIV_ZERO_LONG.invoke(method); + } else { + method.visitInvokeDynamicInsn("ldiv", "(JJ)J", MATHBOOTSTRAP, programPoint); + } return LONG; } @Override public Type rem(final MethodVisitor method, final int programPoint) { - // Never perform non-optimistic integer remainder in JavaScript. - assert programPoint != INVALID_PROGRAM_POINT; - - method.visitInvokeDynamicInsn("lrem", "(JJ)J", MATHBOOTSTRAP, programPoint); + if (programPoint == INVALID_PROGRAM_POINT) { + JSType.REM_ZERO_LONG.invoke(method); + } else { + method.visitInvokeDynamicInsn("lrem", "(JJ)J", MATHBOOTSTRAP, programPoint); + } return LONG; } diff --git a/src/jdk/nashorn/internal/codegen/types/Type.java b/src/jdk/nashorn/internal/codegen/types/Type.java index 332ee55e..2cf2d9b8 100644 --- a/src/jdk/nashorn/internal/codegen/types/Type.java +++ b/src/jdk/nashorn/internal/codegen/types/Type.java @@ -356,7 +356,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl final int pp = input.readInt(); final int typeChar = input.readByte(); final Type type; - switch(typeChar) { + switch (typeChar) { case 'L': type = Type.OBJECT; break; case 'D': type = Type.NUMBER; break; case 'J': type = Type.LONG; break; @@ -376,13 +376,13 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl } private static jdk.internal.org.objectweb.asm.Type lookupInternalType(final Class<?> type) { - final Map<Class<?>, jdk.internal.org.objectweb.asm.Type> cache = INTERNAL_TYPE_CACHE; - jdk.internal.org.objectweb.asm.Type itype = cache.get(type); + final Map<Class<?>, jdk.internal.org.objectweb.asm.Type> c = INTERNAL_TYPE_CACHE; + jdk.internal.org.objectweb.asm.Type itype = c.get(type); if (itype != null) { return itype; } itype = jdk.internal.org.objectweb.asm.Type.getType(type); - cache.put(type, itype); + c.put(type, itype); return itype; } @@ -586,6 +586,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl public int getSlots() { return slots; } + /** * Returns the widest or most common of two types * @@ -609,6 +610,18 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl } /** + * Returns the widest or most common of two types, given as classes + * + * @param type0 type one + * @param type1 type two + * + * @return the widest type + */ + public static Class<?> widest(final Class<?> type0, final Class<?> type1) { + return widest(Type.typeFor(type0), Type.typeFor(type1)).getTypeClass(); + } + + /** * When doing widening for return types of a function or a ternary operator, it is not valid to widen a boolean to * anything other than object. Note that this wouldn't be necessary if {@code Type.widest} did not allow * boolean-to-number widening. Eventually, we should address it there, but it affects too many other parts of the @@ -1142,6 +1155,10 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl return type; } + /** + * Read resolve + * @return resolved type + */ protected final Object readResolve() { return Type.typeFor(clazz); } diff --git a/src/jdk/nashorn/internal/ir/AccessNode.java b/src/jdk/nashorn/internal/ir/AccessNode.java index 315ee395..b8b64820 100644 --- a/src/jdk/nashorn/internal/ir/AccessNode.java +++ b/src/jdk/nashorn/internal/ir/AccessNode.java @@ -28,6 +28,8 @@ package jdk.nashorn.internal.ir; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.parser.Token; +import jdk.nashorn.internal.parser.TokenType; /** * IR representation of a property access (period operator.) @@ -101,6 +103,14 @@ public final class AccessNode extends BaseNode { return property; } + /** + * Return true if this node represents an index operation normally represented as {@link IndexNode}. + * @return true if an index access. + */ + public boolean isIndex() { + return Token.descType(getToken()) == TokenType.LBRACKET; + } + private AccessNode setBase(final Expression base) { if (this.base == base) { return this; diff --git a/src/jdk/nashorn/internal/ir/BinaryNode.java b/src/jdk/nashorn/internal/ir/BinaryNode.java index c2456363..f625fe1a 100644 --- a/src/jdk/nashorn/internal/ir/BinaryNode.java +++ b/src/jdk/nashorn/internal/ir/BinaryNode.java @@ -264,6 +264,10 @@ public final class BinaryNode extends Expression implements Assignment<Expressio case COMMARIGHT: { return rhs.getType(localVariableTypes); } + case AND: + case OR:{ + return Type.widestReturnType(lhs.getType(localVariableTypes), rhs.getType(localVariableTypes)); + } default: if (isComparison()) { return Type.BOOLEAN; @@ -337,10 +341,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio @Override public Node accept(final NodeVisitor<? extends LexicalContext> visitor) { if (visitor.enterBinaryNode(this)) { - if(tokenType().isLeftAssociative()) { - return visitor.leaveBinaryNode(setLHS((Expression)lhs.accept(visitor)).setRHS((Expression)rhs.accept(visitor))); - } - return visitor.leaveBinaryNode(setRHS((Expression)rhs.accept(visitor)).setLHS((Expression)lhs.accept(visitor))); + return visitor.leaveBinaryNode(setLHS((Expression)lhs.accept(visitor)).setRHS((Expression)rhs.accept(visitor))); } return this; diff --git a/src/jdk/nashorn/internal/ir/ForNode.java b/src/jdk/nashorn/internal/ir/ForNode.java index 2847947c..ef6f11db 100644 --- a/src/jdk/nashorn/internal/ir/ForNode.java +++ b/src/jdk/nashorn/internal/ir/ForNode.java @@ -45,14 +45,14 @@ public final class ForNode extends LoopNode { /** Iterator symbol. */ private Symbol iterator; - /** Is this a normal for loop? */ - public static final int IS_FOR = 1 << 0; - /** Is this a normal for in loop? */ - public static final int IS_FOR_IN = 1 << 1; + public static final int IS_FOR_IN = 1 << 0; /** Is this a normal for each in loop? */ - public static final int IS_FOR_EACH = 1 << 2; + public static final int IS_FOR_EACH = 1 << 1; + + /** Does this loop need a per-iteration scope because its init contain a LET declaration? */ + public static final int PER_ITERATION_SCOPE = 1 << 2; private final int flags; @@ -273,4 +273,18 @@ public final class ForNode extends LoopNode { JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) { return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion)); } + + @Override + public boolean hasPerIterationScope() { + return (flags & PER_ITERATION_SCOPE) != 0; + } + + /** + * Set the per-iteration-scope flag on this node. + * @param lc lexical context + * @return the node with flag set + */ + public ForNode setPerIterationScope(final LexicalContext lc) { + return setFlags(lc, flags | PER_ITERATION_SCOPE); + } } diff --git a/src/jdk/nashorn/internal/ir/LexicalContext.java b/src/jdk/nashorn/internal/ir/LexicalContext.java index 2b59a07c..487d6aeb 100644 --- a/src/jdk/nashorn/internal/ir/LexicalContext.java +++ b/src/jdk/nashorn/internal/ir/LexicalContext.java @@ -597,6 +597,20 @@ public class LexicalContext { throw new AssertionError(target + " was expected in lexical context " + LexicalContext.this + " but wasn't"); } + /** + * Checks whether the current context is inside a switch statement without explicit blocks (curly braces). + * @return true if in unprotected switch statement + */ + public boolean inUnprotectedSwitchContext() { + for (int i = sp; i > 0; i--) { + final LexicalContextNode next = stack[i]; + if (next instanceof Block) { + return stack[i - 1] instanceof SwitchNode; + } + } + return false; + } + @Override public String toString() { final StringBuffer sb = new StringBuffer(); diff --git a/src/jdk/nashorn/internal/ir/LoopNode.java b/src/jdk/nashorn/internal/ir/LoopNode.java index e6436ad9..5991a32a 100644 --- a/src/jdk/nashorn/internal/ir/LoopNode.java +++ b/src/jdk/nashorn/internal/ir/LoopNode.java @@ -176,4 +176,10 @@ public abstract class LoopNode extends BreakableStatement { * @return new loop node if changed otherwise the same */ public abstract LoopNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes); + + /** + * Does this loop have a LET declaration and hence require a per-iteration scope? + * @return true if a per-iteration scope is required. + */ + public abstract boolean hasPerIterationScope(); } diff --git a/src/jdk/nashorn/internal/ir/RuntimeNode.java b/src/jdk/nashorn/internal/ir/RuntimeNode.java index fd34c3ec..4eca8ff0 100644 --- a/src/jdk/nashorn/internal/ir/RuntimeNode.java +++ b/src/jdk/nashorn/internal/ir/RuntimeNode.java @@ -27,7 +27,6 @@ package jdk.nashorn.internal.ir; import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -468,11 +467,7 @@ public class RuntimeNode extends Expression implements Optimistic { @Override public Node accept(final NodeVisitor<? extends LexicalContext> visitor) { if (visitor.enterRuntimeNode(this)) { - final List<Expression> newArgs = new ArrayList<>(); - for (final Node arg : args) { - newArgs.add((Expression)arg.accept(visitor)); - } - return visitor.leaveRuntimeNode(setArgs(newArgs)); + return visitor.leaveRuntimeNode(setArgs(Node.accept(visitor, args))); } return this; diff --git a/src/jdk/nashorn/internal/ir/SwitchNode.java b/src/jdk/nashorn/internal/ir/SwitchNode.java index 8936764c..ca447587 100644 --- a/src/jdk/nashorn/internal/ir/SwitchNode.java +++ b/src/jdk/nashorn/internal/ir/SwitchNode.java @@ -48,6 +48,10 @@ public final class SwitchNode extends BreakableStatement { /** Switch default index. */ private final int defaultCaseIndex; + /** True if all cases are 32-bit signed integer constants, without repetitions. It's a prerequisite for + * using a tableswitch/lookupswitch when generating code. */ + private final boolean uniqueInteger; + /** Tag symbol. */ private Symbol tag; @@ -66,15 +70,17 @@ public final class SwitchNode extends BreakableStatement { this.expression = expression; this.cases = cases; this.defaultCaseIndex = defaultCase == null ? -1 : cases.indexOf(defaultCase); + this.uniqueInteger = false; } private SwitchNode(final SwitchNode switchNode, final Expression expression, final List<CaseNode> cases, - final int defaultCaseIndex, final LocalVariableConversion conversion) { + final int defaultCaseIndex, final LocalVariableConversion conversion, final boolean uniqueInteger) { super(switchNode, conversion); this.expression = expression; this.cases = cases; this.defaultCaseIndex = defaultCaseIndex; - this.tag = switchNode.getTag(); //TODO are symbols inhereted as references? + this.tag = switchNode.getTag(); //TODO are symbols inherited as references? + this.uniqueInteger = uniqueInteger; } @Override @@ -83,7 +89,7 @@ public final class SwitchNode extends BreakableStatement { for (final CaseNode caseNode : cases) { newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody(), caseNode.getLocalVariableConversion())); } - return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion)); + return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion, uniqueInteger)); } @Override @@ -151,7 +157,7 @@ public final class SwitchNode extends BreakableStatement { if (this.cases == cases) { return this; } - return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion)); + return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger)); } /** @@ -183,7 +189,7 @@ public final class SwitchNode extends BreakableStatement { if (this.expression == expression) { return this; } - return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion)); + return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger)); } /** @@ -205,25 +211,30 @@ public final class SwitchNode extends BreakableStatement { } /** - * Returns true if all cases of this switch statement are 32-bit signed integer constants. - * @return true if all cases of this switch statement are 32-bit signed integer constants. + * Returns true if all cases of this switch statement are 32-bit signed integer constants, without repetitions. + * @return true if all cases of this switch statement are 32-bit signed integer constants, without repetitions. */ - public boolean isInteger() { - for (final CaseNode caseNode : cases) { - final Expression test = caseNode.getTest(); - if (test != null && !isIntegerLiteral(test)) { - return false; - } + public boolean isUniqueInteger() { + return uniqueInteger; + } + + /** + * Sets whether all cases of this switch statement are 32-bit signed integer constants, without repetitions. + * @param lc lexical context + * @param uniqueInteger if true, all cases of this switch statement have been determined to be 32-bit signed + * integer constants, without repetitions. + * @return this switch node, if the value didn't change, or a new switch node with the changed value + */ + public SwitchNode setUniqueInteger(final LexicalContext lc, final boolean uniqueInteger) { + if(this.uniqueInteger == uniqueInteger) { + return this; } - return true; + return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger)); } @Override JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) { - return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion)); + return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger)); } - private static boolean isIntegerLiteral(final Expression expr) { - return expr instanceof LiteralNode && ((LiteralNode<?>)expr).getValue() instanceof Integer; - } } diff --git a/src/jdk/nashorn/internal/ir/VarNode.java b/src/jdk/nashorn/internal/ir/VarNode.java index 44d7d4c7..1cee8cb5 100644 --- a/src/jdk/nashorn/internal/ir/VarNode.java +++ b/src/jdk/nashorn/internal/ir/VarNode.java @@ -45,19 +45,16 @@ public final class VarNode extends Statement implements Assignment<IdentNode> { /** Is this a var statement (as opposed to a "var" in a for loop statement) */ private final int flags; - /** Flag that determines if this function node is a statement */ - public static final int IS_STATEMENT = 1 << 0; - /** Flag for ES6 LET declaration */ - public static final int IS_LET = 1 << 1; + public static final int IS_LET = 1 << 0; /** Flag for ES6 CONST declaration */ - public static final int IS_CONST = 1 << 2; + public static final int IS_CONST = 1 << 1; /** Flag that determines if this is the last function declaration in a function * This is used to micro optimize the placement of return value assignments for * a program node */ - public static final int IS_LAST_FUNCTION_DECLARATION = 1 << 3; + public static final int IS_LAST_FUNCTION_DECLARATION = 1 << 2; /** * Constructor @@ -69,7 +66,7 @@ public final class VarNode extends Statement implements Assignment<IdentNode> { * @param init init node or null if just a declaration */ public VarNode(final int lineNumber, final long token, final int finish, final IdentNode name, final Expression init) { - this(lineNumber, token, finish, name, init, IS_STATEMENT); + this(lineNumber, token, finish, name, init, 0); } private VarNode(final VarNode varNode, final IdentNode name, final Expression init, final int flags) { @@ -260,14 +257,6 @@ public final class VarNode extends Statement implements Assignment<IdentNode> { } /** - * Returns true if this is a var statement (as opposed to a var initializer in a for loop). - * @return true if this is a var statement (as opposed to a var initializer in a for loop). - */ - public boolean isStatement() { - return (flags & IS_STATEMENT) != 0; - } - - /** * Returns true if this is a function declaration. * @return true if this is a function declaration. */ diff --git a/src/jdk/nashorn/internal/ir/WhileNode.java b/src/jdk/nashorn/internal/ir/WhileNode.java index 9a0981fb..7e60fee0 100644 --- a/src/jdk/nashorn/internal/ir/WhileNode.java +++ b/src/jdk/nashorn/internal/ir/WhileNode.java @@ -148,4 +148,9 @@ public final class WhileNode extends LoopNode { } return test == null; } + + @Override + public boolean hasPerIterationScope() { + return false; + } } diff --git a/src/jdk/nashorn/internal/objects/ArrayBufferView.java b/src/jdk/nashorn/internal/objects/ArrayBufferView.java index e33fac3b..fa3b8071 100644 --- a/src/jdk/nashorn/internal/objects/ArrayBufferView.java +++ b/src/jdk/nashorn/internal/objects/ArrayBufferView.java @@ -28,7 +28,6 @@ package jdk.nashorn.internal.objects; import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import jdk.internal.dynalink.CallSiteDescriptor; @@ -44,6 +43,9 @@ import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.arrays.ArrayData; import jdk.nashorn.internal.runtime.arrays.TypedArrayData; +/** + * ArrayBufferView, es6 class or TypedArray implementation + */ @ScriptClass("ArrayBufferView") public abstract class ArrayBufferView extends ScriptObject { private final NativeArrayBuffer buffer; @@ -71,6 +73,13 @@ public abstract class ArrayBufferView extends ScriptObject { setArray(data); } + /** + * Constructor + * + * @param buffer underlying NativeArrayBuffer + * @param byteOffset byte offset for buffer + * @param elementLength element length in bytes + */ protected ArrayBufferView(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength) { this(buffer, byteOffset, elementLength, Global.instance()); } @@ -89,22 +98,42 @@ public abstract class ArrayBufferView extends ScriptObject { return factory().bytesPerElement; } + /** + * Buffer getter as per spec + * @param self ArrayBufferView instance + * @return buffer + */ @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) public static Object buffer(final Object self) { return ((ArrayBufferView)self).buffer; } + /** + * Buffer offset getter as per spec + * @param self ArrayBufferView instance + * @return buffer offset + */ @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) public static int byteOffset(final Object self) { return ((ArrayBufferView)self).byteOffset; } + /** + * Byte length getter as per spec + * @param self ArrayBufferView instance + * @return array buffer view length in bytes + */ @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) public static int byteLength(final Object self) { final ArrayBufferView view = (ArrayBufferView)self; return ((TypedArrayData<?>)view.getArray()).getElementLength() * view.bytesPerElement(); } + /** + * Length getter as per spec + * @param self ArrayBufferView instance + * @return length in elements + */ @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) public static int length(final Object self) { return ((ArrayBufferView)self).elementLength(); @@ -119,15 +148,29 @@ public abstract class ArrayBufferView extends ScriptObject { return ((TypedArrayData<?>)getArray()).getElementLength(); } + /** + * Factory class for byte ArrayBufferViews + */ protected static abstract class Factory { final int bytesPerElement; final int maxElementLength; + /** + * Constructor + * + * @param bytesPerElement number of bytes per element for this buffer + */ public Factory(final int bytesPerElement) { this.bytesPerElement = bytesPerElement; this.maxElementLength = Integer.MAX_VALUE / bytesPerElement; } + /** + * Factory method + * + * @param elementLength number of elements + * @return new ArrayBufferView + */ public final ArrayBufferView construct(final int elementLength) { if (elementLength > maxElementLength) { throw rangeError("inappropriate.array.buffer.length", JSType.toString(elementLength)); @@ -135,15 +178,47 @@ public abstract class ArrayBufferView extends ScriptObject { return construct(new NativeArrayBuffer(elementLength * bytesPerElement), 0, elementLength); } - public abstract ArrayBufferView construct(NativeArrayBuffer buffer, int byteOffset, int elementLength); - - public abstract TypedArrayData<?> createArrayData(ByteBuffer nb, int start, int end); - + /** + * Factory method + * + * @param buffer underlying buffer + * @param byteOffset byte offset + * @param elementLength number of elements + * + * @return new ArrayBufferView + */ + public abstract ArrayBufferView construct(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength); + + /** + * Factory method for array data + * + * @param nb underlying nativebuffer + * @param start start element + * @param end end element + * + * @return new array data + */ + public abstract TypedArrayData<?> createArrayData(final ByteBuffer nb, final int start, final int end); + + /** + * Get the class name for this type of buffer + * + * @return class name + */ public abstract String getClassName(); } + /** + * Get the factor for this kind of buffer + * @return Factory + */ protected abstract Factory factory(); + /** + * Get the prototype for this ArrayBufferView + * @param global global instance + * @return prototype + */ protected abstract ScriptObject getPrototype(final Global global); @Override @@ -151,10 +226,23 @@ public abstract class ArrayBufferView extends ScriptObject { return factory().getClassName(); } + /** + * Check if this array contains floats + * @return true if float array (or double) + */ protected boolean isFloatArray() { return false; } + /** + * Inheritable constructor implementation + * + * @param newObj is this a new constructor + * @param args arguments + * @param factory factory + * + * @return new ArrayBufferView + */ protected static ArrayBufferView constructorImpl(final boolean newObj, final Object[] args, final Factory factory) { final Object arg0 = args.length != 0 ? args[0] : 0; final ArrayBufferView dest; @@ -200,6 +288,15 @@ public abstract class ArrayBufferView extends ScriptObject { return dest; } + /** + * Inheritable implementation of set, if no efficient implementation is available + * + * @param self ArrayBufferView instance + * @param array array + * @param offset0 array offset + * + * @return result of setter + */ protected static Object setImpl(final Object self, final Object array, final Object offset0) { final ArrayBufferView dest = (ArrayBufferView)self; final int length; @@ -244,6 +341,15 @@ public abstract class ArrayBufferView extends ScriptObject { return (int)(length & Integer.MAX_VALUE); } + /** + * Implementation of subarray if no efficient override exists + * + * @param self ArrayBufferView instance + * @param begin0 begin index + * @param end0 end index + * + * @return sub array + */ protected static ScriptObject subarrayImpl(final Object self, final Object begin0, final Object end0) { final ArrayBufferView arrayView = (ArrayBufferView)self; final int byteOffset = arrayView.byteOffset; diff --git a/src/jdk/nashorn/internal/objects/Global.java b/src/jdk/nashorn/internal/objects/Global.java index eb56de0e..bb3e13d2 100644 --- a/src/jdk/nashorn/internal/objects/Global.java +++ b/src/jdk/nashorn/internal/objects/Global.java @@ -29,10 +29,12 @@ import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; + import java.io.IOException; import java.io.PrintWriter; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.invoke.SwitchPoint; import java.lang.reflect.Field; import java.util.ArrayList; @@ -41,9 +43,9 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; import javax.script.ScriptContext; import javax.script.ScriptEngine; +import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; import jdk.nashorn.api.scripting.ClassFilter; @@ -54,6 +56,7 @@ import jdk.nashorn.internal.objects.annotations.Property; import jdk.nashorn.internal.objects.annotations.ScriptClass; import jdk.nashorn.internal.runtime.ConsString; import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ECMAErrors; import jdk.nashorn.internal.runtime.GlobalConstants; import jdk.nashorn.internal.runtime.GlobalFunctions; import jdk.nashorn.internal.runtime.JSType; @@ -70,6 +73,7 @@ import jdk.nashorn.internal.runtime.Specialization; import jdk.nashorn.internal.runtime.arrays.ArrayData; import jdk.nashorn.internal.runtime.linker.Bootstrap; import jdk.nashorn.internal.runtime.linker.InvokeByName; +import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; import jdk.nashorn.internal.runtime.regexp.RegExpResult; import jdk.nashorn.internal.scripts.JO; @@ -411,13 +415,14 @@ public final class Global extends ScriptObject implements Scope { // Used to store the last RegExp result to support deprecated RegExp constructor properties private RegExpResult lastRegExpResult; - private static final MethodHandle EVAL = findOwnMH_S("eval", Object.class, Object.class, Object.class); - private static final MethodHandle NO_SUCH_PROPERTY = findOwnMH_S(NO_SUCH_PROPERTY_NAME, Object.class, Object.class, Object.class); - private static final MethodHandle PRINT = findOwnMH_S("print", Object.class, Object.class, Object[].class); - private static final MethodHandle PRINTLN = findOwnMH_S("println", Object.class, Object.class, Object[].class); - private static final MethodHandle LOAD = findOwnMH_S("load", Object.class, Object.class, Object.class); - private static final MethodHandle LOADWITHNEWGLOBAL = findOwnMH_S("loadWithNewGlobal", Object.class, Object.class, Object[].class); - private static final MethodHandle EXIT = findOwnMH_S("exit", Object.class, Object.class, Object.class); + private static final MethodHandle EVAL = findOwnMH_S("eval", Object.class, Object.class, Object.class); + private static final MethodHandle NO_SUCH_PROPERTY = findOwnMH_S(NO_SUCH_PROPERTY_NAME, Object.class, Object.class, Object.class); + private static final MethodHandle PRINT = findOwnMH_S("print", Object.class, Object.class, Object[].class); + private static final MethodHandle PRINTLN = findOwnMH_S("println", Object.class, Object.class, Object[].class); + private static final MethodHandle LOAD = findOwnMH_S("load", Object.class, Object.class, Object.class); + private static final MethodHandle LOAD_WITH_NEW_GLOBAL = findOwnMH_S("loadWithNewGlobal", Object.class, Object.class, Object[].class); + private static final MethodHandle EXIT = findOwnMH_S("exit", Object.class, Object.class, Object.class); + private static final MethodHandle LEXICAL_SCOPE_FILTER = findOwnMH_S("lexicalScopeFilter", Object.class, Object.class); // initialized by nasgen private static PropertyMap $nasgenmap$; @@ -430,6 +435,12 @@ public final class Global extends ScriptObject implements Scope { // current ScriptEngine associated - can be null. private ScriptEngine engine; + // ES6 global lexical scope. + private final LexicalScope lexicalScope; + + // Switchpoint for non-constant global callsites in the presence of ES6 lexical scope. + private SwitchPoint lexicalScopeSwitchPoint; + /** * Set the current script context * @param scontext script context @@ -438,9 +449,6 @@ public final class Global extends ScriptObject implements Scope { this.scontext = scontext; } - // global constants for this global - they can be replaced with MethodHandle.constant until invalidated - private static AtomicReference<GlobalConstants> gcsInstance = new AtomicReference<>(); - @Override protected Context getContext() { return context; @@ -470,11 +478,7 @@ public final class Global extends ScriptObject implements Scope { super(checkAndGetMap(context)); this.context = context; this.setIsScope(); - //we can only share one instance of Global constants between globals, or we consume way too much - //memory - this is good enough for most programs - while (gcsInstance.get() == null) { - gcsInstance.compareAndSet(null, new GlobalConstants(context.getLogger(GlobalConstants.class))); - } + this.lexicalScope = context.getEnv()._es6 ? new LexicalScope(this) : null; } /** @@ -493,15 +497,6 @@ public final class Global extends ScriptObject implements Scope { } /** - * Return the global constants map for fields that - * can be accessed as MethodHandle.constant - * @return constant map - */ - public static GlobalConstants getConstants() { - return gcsInstance.get(); - } - - /** * Check if we have a Global instance * @return true if one exists */ @@ -1712,6 +1707,133 @@ public final class Global extends ScriptObject implements Scope { splitState = state; } + /** + * Return the ES6 global scope for lexically declared bindings. + * @return the ES6 lexical global scope. + */ + public final ScriptObject getLexicalScope() { + assert context.getEnv()._es6; + return lexicalScope; + } + + @Override + public void addBoundProperties(final ScriptObject source, final jdk.nashorn.internal.runtime.Property[] properties) { + PropertyMap ownMap = getMap(); + LexicalScope lexicalScope = null; + PropertyMap lexicalMap = null; + boolean hasLexicalDefinitions = false; + + if (context.getEnv()._es6) { + lexicalScope = (LexicalScope) getLexicalScope(); + lexicalMap = lexicalScope.getMap(); + + for (final jdk.nashorn.internal.runtime.Property property : properties) { + if (property.isLexicalBinding()) { + hasLexicalDefinitions = true; + } + // ES6 15.1.8 steps 6. and 7. + final jdk.nashorn.internal.runtime.Property globalProperty = ownMap.findProperty(property.getKey()); + if (globalProperty != null && !globalProperty.isConfigurable() && property.isLexicalBinding()) { + throw ECMAErrors.syntaxError("redeclare.variable", property.getKey()); + } + final jdk.nashorn.internal.runtime.Property lexicalProperty = lexicalMap.findProperty(property.getKey()); + if (lexicalProperty != null && !property.isConfigurable()) { + throw ECMAErrors.syntaxError("redeclare.variable", property.getKey()); + } + } + } + + for (final jdk.nashorn.internal.runtime.Property property : properties) { + if (property.isLexicalBinding()) { + assert lexicalScope != null; + lexicalMap = lexicalScope.addBoundProperty(lexicalMap, source, property); + + if (ownMap.findProperty(property.getKey()) != null) { + // If property exists in the global object invalidate any global constant call sites. + invalidateGlobalConstant(property.getKey()); + } + } else { + ownMap = addBoundProperty(ownMap, source, property); + } + } + + setMap(ownMap); + + if (hasLexicalDefinitions) { + lexicalScope.setMap(lexicalMap); + invalidateLexicalSwitchPoint(); + } + } + + @Override + public GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) { + final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); + final boolean isScope = NashornCallSiteDescriptor.isScope(desc); + + if (lexicalScope != null && isScope && !NashornCallSiteDescriptor.isApplyToCall(desc)) { + if (lexicalScope.hasOwnProperty(name)) { + return lexicalScope.findGetMethod(desc, request, operator); + } + } + + final GuardedInvocation invocation = super.findGetMethod(desc, request, operator); + + // We want to avoid adding our generic lexical scope switchpoint to global constant invocations, + // because those are invalidated per-key in the addBoundProperties method above. + // We therefor check if the invocation does already have a switchpoint and the property is non-inherited, + // assuming this only applies to global constants. If other non-inherited properties will + // start using switchpoints some time in the future we'll have to revisit this. + if (isScope && context.getEnv()._es6 && (invocation.getSwitchPoints() == null || !hasOwnProperty(name))) { + return invocation.addSwitchPoint(getLexicalScopeSwitchPoint()); + } + + return invocation; + } + + @Override + public GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) { + final boolean isScope = NashornCallSiteDescriptor.isScope(desc); + + if (lexicalScope != null && isScope) { + final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); + if (lexicalScope.hasOwnProperty(name)) { + return lexicalScope.findSetMethod(desc, request); + } + } + + final GuardedInvocation invocation = super.findSetMethod(desc, request); + + if (isScope && context.getEnv()._es6) { + return invocation.addSwitchPoint(getLexicalScopeSwitchPoint()); + } + + return invocation; + } + + private synchronized SwitchPoint getLexicalScopeSwitchPoint() { + SwitchPoint switchPoint = lexicalScopeSwitchPoint; + if (switchPoint == null || switchPoint.hasBeenInvalidated()) { + switchPoint = lexicalScopeSwitchPoint = new SwitchPoint(); + } + return switchPoint; + } + + private synchronized void invalidateLexicalSwitchPoint() { + if (lexicalScopeSwitchPoint != null) { + context.getLogger(GlobalConstants.class).info("Invalidating non-constant globals on lexical scope update"); + SwitchPoint.invalidateAll(new SwitchPoint[]{ lexicalScopeSwitchPoint }); + } + } + + + @SuppressWarnings("unused") + private static Object lexicalScopeFilter(final Object self) { + if (self instanceof Global) { + return ((Global) self).getLexicalScope(); + } + return self; + } + private <T extends ScriptObject> T initConstructorAndSwitchPoint(final String name, final Class<T> clazz) { final T func = initConstructor(name, clazz); tagBuiltinProperties(name, func); @@ -1757,7 +1879,7 @@ public final class Global extends ScriptObject implements Scope { this.unescape = ScriptFunctionImpl.makeFunction("unescape", GlobalFunctions.UNESCAPE); this.print = ScriptFunctionImpl.makeFunction("print", env._print_no_newline ? PRINT : PRINTLN); this.load = ScriptFunctionImpl.makeFunction("load", LOAD); - this.loadWithNewGlobal = ScriptFunctionImpl.makeFunction("loadWithNewGlobal", LOADWITHNEWGLOBAL); + this.loadWithNewGlobal = ScriptFunctionImpl.makeFunction("loadWithNewGlobal", LOAD_WITH_NEW_GLOBAL); this.exit = ScriptFunctionImpl.makeFunction("exit", EXIT); this.quit = ScriptFunctionImpl.makeFunction("quit", EXIT); @@ -2223,4 +2345,36 @@ public final class Global extends ScriptObject implements Scope { protected boolean isGlobal() { return true; } + + /** + * A class representing the ES6 global lexical scope. + */ + private static class LexicalScope extends ScriptObject { + + LexicalScope(final ScriptObject proto) { + super(proto, PropertyMap.newMap()); + } + + @Override + protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) { + return filterInvocation(super.findGetMethod(desc, request, operator)); + } + + @Override + protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) { + return filterInvocation(super.findSetMethod(desc, request)); + } + + @Override + protected PropertyMap addBoundProperty(final PropertyMap propMap, final ScriptObject source, final jdk.nashorn.internal.runtime.Property property) { + // We override this method just to make it callable by Global + return super.addBoundProperty(propMap, source, property); + } + + private static GuardedInvocation filterInvocation(final GuardedInvocation invocation) { + final MethodType type = invocation.getInvocation().type(); + return invocation.asType(type.changeParameterType(0, Object.class)).filterArguments(0, LEXICAL_SCOPE_FILTER); + } + } + } diff --git a/src/jdk/nashorn/internal/objects/NativeArray.java b/src/jdk/nashorn/internal/objects/NativeArray.java index c83e8883..047d612a 100644 --- a/src/jdk/nashorn/internal/objects/NativeArray.java +++ b/src/jdk/nashorn/internal/objects/NativeArray.java @@ -33,8 +33,8 @@ import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; import static jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator.arrayLikeIterator; import static jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator.reverseArrayLikeIterator; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT; + import java.lang.invoke.MethodHandle; -import java.lang.invoke.SwitchPoint; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -92,16 +92,6 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin private static final Object CALL_CMP = new Object(); private static final Object TO_LOCALE_STRING = new Object(); - private SwitchPoint lengthMadeNotWritableSwitchPoint; - private PushLinkLogic pushLinkLogic; - private PopLinkLogic popLinkLogic; - - /** - * Index for the modification SwitchPoint that triggers when length - * becomes not writable - */ - private static final int LENGTH_NOT_WRITABLE_SWITCHPOINT = 0; - /* * Constructors. */ @@ -130,7 +120,9 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin this(ArrayData.allocate(array.length)); ArrayData arrayData = this.getArray(); - arrayData.ensure(array.length - 1); + if (array.length > 0) { + arrayData.ensure(array.length - 1); + } for (int index = 0; index < array.length; index++) { final Object value = array[index]; @@ -266,13 +258,84 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin @Override public Object getLength() { - final long length = getArray().length() & JSType.MAX_UINT; - if(length < Integer.MAX_VALUE) { + final long length = JSType.toUint32(getArray().length()); + if (length < Integer.MAX_VALUE) { return (int)length; } return length; } + private boolean defineLength(final long oldLen, final PropertyDescriptor oldLenDesc, final PropertyDescriptor desc, final boolean reject) { + // Step 3a + if (!desc.has(VALUE)) { + return super.defineOwnProperty("length", desc, reject); + } + + // Step 3b + final PropertyDescriptor newLenDesc = desc; + + // Step 3c and 3d - get new length and convert to long + final long newLen = NativeArray.validLength(newLenDesc.getValue(), true); + + // Step 3e + newLenDesc.setValue(newLen); + + // Step 3f + // increasing array length - just need to set new length value (and attributes if any) and return + if (newLen >= oldLen) { + return super.defineOwnProperty("length", newLenDesc, reject); + } + + // Step 3g + if (!oldLenDesc.isWritable()) { + if (reject) { + throw typeError("property.not.writable", "length", ScriptRuntime.safeToString(this)); + } + return false; + } + + // Step 3h and 3i + final boolean newWritable = !newLenDesc.has(WRITABLE) || newLenDesc.isWritable(); + if (!newWritable) { + newLenDesc.setWritable(true); + } + + // Step 3j and 3k + final boolean succeeded = super.defineOwnProperty("length", newLenDesc, reject); + if (!succeeded) { + return false; + } + + // Step 3l + // make sure that length is set till the point we can delete the old elements + long o = oldLen; + while (newLen < o) { + o--; + final boolean deleteSucceeded = delete(o, false); + if (!deleteSucceeded) { + newLenDesc.setValue(o + 1); + if (!newWritable) { + newLenDesc.setWritable(false); + } + super.defineOwnProperty("length", newLenDesc, false); + if (reject) { + throw typeError("property.not.writable", "length", ScriptRuntime.safeToString(this)); + } + return false; + } + } + + // Step 3m + if (!newWritable) { + // make 'length' property not writable + final ScriptObject newDesc = Global.newEmptyInstance(); + newDesc.set(WRITABLE, false, 0); + return super.defineOwnProperty("length", newDesc, false); + } + + return true; + } + /** * ECMA 15.4.5.1 [[DefineOwnProperty]] ( P, Desc, Throw ) */ @@ -286,82 +349,16 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin // Step 2 // get old length and convert to long - long oldLen = NativeArray.validLength(oldLenDesc.getValue(), true); + final long oldLen = NativeArray.validLength(oldLenDesc.getValue(), true); // Step 3 if ("length".equals(key)) { // check for length being made non-writable + final boolean result = defineLength(oldLen, oldLenDesc, desc, reject); if (desc.has(WRITABLE) && !desc.isWritable()) { setIsLengthNotWritable(); } - - // Step 3a - if (!desc.has(VALUE)) { - return super.defineOwnProperty("length", desc, reject); - } - - // Step 3b - final PropertyDescriptor newLenDesc = desc; - - // Step 3c and 3d - get new length and convert to long - final long newLen = NativeArray.validLength(newLenDesc.getValue(), true); - - // Step 3e - newLenDesc.setValue(newLen); - - // Step 3f - // increasing array length - just need to set new length value (and attributes if any) and return - if (newLen >= oldLen) { - return super.defineOwnProperty("length", newLenDesc, reject); - } - - // Step 3g - if (!oldLenDesc.isWritable()) { - if (reject) { - throw typeError("property.not.writable", "length", ScriptRuntime.safeToString(this)); - } - return false; - } - - // Step 3h and 3i - final boolean newWritable = !newLenDesc.has(WRITABLE) || newLenDesc.isWritable(); - if (!newWritable) { - newLenDesc.setWritable(true); - } - - // Step 3j and 3k - final boolean succeeded = super.defineOwnProperty("length", newLenDesc, reject); - if (!succeeded) { - return false; - } - - // Step 3l - // make sure that length is set till the point we can delete the old elements - while (newLen < oldLen) { - oldLen--; - final boolean deleteSucceeded = delete(oldLen, false); - if (!deleteSucceeded) { - newLenDesc.setValue(oldLen + 1); - if (!newWritable) { - newLenDesc.setWritable(false); - } - super.defineOwnProperty("length", newLenDesc, false); - if (reject) { - throw typeError("property.not.writable", "length", ScriptRuntime.safeToString(this)); - } - return false; - } - } - - // Step 3m - if (!newWritable) { - // make 'length' property not writable - final ScriptObject newDesc = Global.newEmptyInstance(); - newDesc.set(WRITABLE, false, 0); - return super.defineOwnProperty("length", newDesc, false); - } - - return true; + return result; } // Step 4a @@ -437,23 +434,7 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin @Override public void setIsLengthNotWritable() { super.setIsLengthNotWritable(); - /* - * Switchpoints are created lazily. If we link any push or pop site, - * we need to create the "length made not writable" switchpoint, if it - * doesn't exist. - * - * If the switchpoint already exists, we will find it here, and invalidate - * it, invalidating all previous callsites that use it. - * - * If the switchpoint doesn't exist, no push/pop has been linked so far, - * because that would create it too. We invalidate it immediately and the - * check link logic for all future callsites will fail immediately at link - * time - */ - if (lengthMadeNotWritableSwitchPoint == null) { - lengthMadeNotWritableSwitchPoint = new SwitchPoint(); - } - SwitchPoint.invalidateAll(new SwitchPoint[] { lengthMadeNotWritableSwitchPoint }); + setArray(ArrayData.setIsLengthNotWritable(getArray())); } /** @@ -476,7 +457,7 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE) public static Object length(final Object self) { if (isArray(self)) { - return ((ScriptObject) self).getArray().length() & JSType.MAX_UINT; + return JSType.toUint32(((ScriptObject) self).getArray().length()); } return 0; @@ -490,7 +471,7 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin @Setter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE) public static void length(final Object self, final Object length) { if (isArray(self)) { - ((ScriptObject) self).setLength(validLength(length, true)); + ((ScriptObject)self).setLength(validLength(length, true)); } } @@ -757,12 +738,86 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin * ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] ) * * @param self self reference + * @param arg argument + * @return resulting NativeArray + */ + @SpecializedFunction(linkLogic=ConcatLinkLogic.class) + public static NativeArray concat(final Object self, final int arg) { + final ContinuousArrayData newData = getContinuousArrayDataCCE(self, Integer.class).copy(); //get at least an integer data copy of this data + newData.fastPush(arg); //add an integer to its end + return new NativeArray(newData); + } + + /** + * ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] ) + * + * @param self self reference + * @param arg argument + * @return resulting NativeArray + */ + @SpecializedFunction(linkLogic=ConcatLinkLogic.class) + public static NativeArray concat(final Object self, final long arg) { + final ContinuousArrayData newData = getContinuousArrayDataCCE(self, Long.class).copy(); //get at least a long array data copy of this data + newData.fastPush(arg); //add a long at the end + return new NativeArray(newData); + } + + /** + * ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] ) + * + * @param self self reference + * @param arg argument + * @return resulting NativeArray + */ + @SpecializedFunction(linkLogic=ConcatLinkLogic.class) + public static NativeArray concat(final Object self, final double arg) { + final ContinuousArrayData newData = getContinuousArrayDataCCE(self, Double.class).copy(); //get at least a number array data copy of this data + newData.fastPush(arg); //add a double at the end + return new NativeArray(newData); + } + + /** + * ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] ) + * + * @param self self reference + * @param arg argument + * @return resulting NativeArray + */ + @SpecializedFunction(linkLogic=ConcatLinkLogic.class) + public static NativeArray concat(final Object self, final Object arg) { + //arg is [NativeArray] of same type. + final ContinuousArrayData selfData = getContinuousArrayDataCCE(self); + final ContinuousArrayData newData; + + if (arg instanceof NativeArray) { + final ContinuousArrayData argData = (ContinuousArrayData)((NativeArray)arg).getArray(); + if (argData.isEmpty()) { + newData = selfData.copy(); + } else if (selfData.isEmpty()) { + newData = argData.copy(); + } else { + final Class<?> widestElementType = selfData.widest(argData).getBoxedElementType(); + newData = ((ContinuousArrayData)selfData.convert(widestElementType)).fastConcat((ContinuousArrayData)argData.convert(widestElementType)); + } + } else { + newData = getContinuousArrayDataCCE(self, Object.class).copy(); + newData.fastPush(arg); + } + + return new NativeArray(newData); + } + + /** + * ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] ) + * + * @param self self reference * @param args arguments * @return resulting NativeArray */ @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) public static NativeArray concat(final Object self, final Object... args) { final ArrayList<Object> list = new ArrayList<>(); + concatToList(list, Global.toObject(self)); for (final Object obj : args) { @@ -1228,10 +1283,13 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin // Get only non-missing elements. Missing elements go at the end // of the sorted array. So, just don't copy these to sort input. final ArrayList<Object> src = new ArrayList<>(); - for (long i = 0; i < len; i = array.nextIndex(i)) { - if (array.has((int) i)) { - src.add(array.getObject((int) i)); + + for (final Iterator<Long> iter = array.indexIterator(); iter.hasNext(); ) { + final long index = iter.next(); + if (index >= len) { + break; } + src.add(array.getObject((int)index)); } final Object[] sorted = sort(src.toArray(), comparefn); @@ -1689,16 +1747,18 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin @Override public SpecializedFunction.LinkLogic getLinkLogic(final Class<? extends LinkLogic> clazz) { if (clazz == PushLinkLogic.class) { - return pushLinkLogic == null ? new PushLinkLogic(this) : pushLinkLogic; + return PushLinkLogic.INSTANCE; } else if (clazz == PopLinkLogic.class) { - return popLinkLogic == null ? new PopLinkLogic(this) : pushLinkLogic; + return PopLinkLogic.INSTANCE; + } else if (clazz == ConcatLinkLogic.class) { + return ConcatLinkLogic.INSTANCE; } return null; } @Override public boolean hasPerInstanceAssumptions() { - return true; //length switchpoint + return true; //length writable switchpoint } /** @@ -1707,21 +1767,7 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin * modification switchpoint which is touched when length is written. */ private static abstract class ArrayLinkLogic extends SpecializedFunction.LinkLogic { - private final NativeArray array; - - protected ArrayLinkLogic(final NativeArray array) { - this.array = array; - } - - private SwitchPoint getSwitchPoint() { - return array.lengthMadeNotWritableSwitchPoint; - } - - private SwitchPoint newSwitchPoint() { - assert array.lengthMadeNotWritableSwitchPoint == null; - final SwitchPoint sp = new SwitchPoint(); - array.lengthMadeNotWritableSwitchPoint = sp; - return sp; + protected ArrayLinkLogic() { } protected static ContinuousArrayData getContinuousArrayData(final Object self) { @@ -1742,58 +1788,37 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin public Class<? extends Throwable> getRelinkException() { return ClassCastException.class; } + } - @Override - public boolean hasModificationSwitchPoints() { - return getSwitchPoint() != null; - } + /** + * This is linker logic for optimistic concatenations + */ + private static final class ConcatLinkLogic extends ArrayLinkLogic { + private static final LinkLogic INSTANCE = new ConcatLinkLogic(); @Override - public boolean hasModificationSwitchPoint(final int index) { - assert index == LENGTH_NOT_WRITABLE_SWITCHPOINT; - return hasModificationSwitchPoints(); - } + public boolean canLink(final Object self, final CallSiteDescriptor desc, final LinkRequest request) { + final Object[] args = request.getArguments(); - @Override - public SwitchPoint getOrCreateModificationSwitchPoint(final int index) { - assert index == LENGTH_NOT_WRITABLE_SWITCHPOINT; - SwitchPoint sp = getSwitchPoint(); - if (sp == null) { - sp = newSwitchPoint(); + if (args.length != 3) { //single argument check + return false; } - return sp; - } - @Override - public SwitchPoint[] getOrCreateModificationSwitchPoints() { - return new SwitchPoint[] { getOrCreateModificationSwitchPoint(LENGTH_NOT_WRITABLE_SWITCHPOINT) }; - } - - @Override - public void invalidateModificationSwitchPoint(final int index) { - assert index == LENGTH_NOT_WRITABLE_SWITCHPOINT; - invalidateModificationSwitchPoints(); - } - - @Override - public void invalidateModificationSwitchPoints() { - final SwitchPoint sp = getSwitchPoint(); - assert sp != null : "trying to invalidate non-existant modified SwitchPoint"; - if (!sp.hasBeenInvalidated()) { - SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); + final ContinuousArrayData selfData = getContinuousArrayData(self); + if (selfData == null) { + return false; } - } - @Override - public boolean hasInvalidatedModificationSwitchPoint(final int index) { - assert index == LENGTH_NOT_WRITABLE_SWITCHPOINT; - return hasInvalidatedModificationSwitchPoints(); - } + final Object arg = args[2]; + //args[2] continuousarray or non arraydata, let past non array datas + if (arg instanceof NativeArray) { + final ContinuousArrayData argData = getContinuousArrayData(arg); + if (argData == null) { + return false; + } + } - @Override - public boolean hasInvalidatedModificationSwitchPoints() { - final SwitchPoint sp = getSwitchPoint(); - return sp != null && !sp.hasBeenInvalidated(); + return true; } } @@ -1801,9 +1826,7 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin * This is linker logic for optimistic pushes */ private static final class PushLinkLogic extends ArrayLinkLogic { - private PushLinkLogic(final NativeArray array) { - super(array); - } + private static final LinkLogic INSTANCE = new PushLinkLogic(); @Override public boolean canLink(final Object self, final CallSiteDescriptor desc, final LinkRequest request) { @@ -1815,9 +1838,7 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin * This is linker logic for optimistic pops */ private static final class PopLinkLogic extends ArrayLinkLogic { - private PopLinkLogic(final NativeArray array) { - super(array); - } + private static final LinkLogic INSTANCE = new PopLinkLogic(); /** * We need to check if we are dealing with a continuous non empty array data here, @@ -1864,6 +1885,14 @@ public final class NativeArray extends ScriptObject implements OptimisticBuiltin throw new ClassCastException(); } + private static final ContinuousArrayData getContinuousArrayDataCCE(final Object self) { + try { + return (ContinuousArrayData)((NativeArray)self).getArray(); + } catch (final NullPointerException e) { + throw new ClassCastException(); + } + } + private static final ContinuousArrayData getContinuousArrayDataCCE(final Object self, final Class<?> elementType) { try { return (ContinuousArrayData)((NativeArray)self).getArray(elementType); //ensure element type can fit "elementType" diff --git a/src/jdk/nashorn/internal/objects/NativeArrayBuffer.java b/src/jdk/nashorn/internal/objects/NativeArrayBuffer.java index 4d16ca54..e3c5c00d 100644 --- a/src/jdk/nashorn/internal/objects/NativeArrayBuffer.java +++ b/src/jdk/nashorn/internal/objects/NativeArrayBuffer.java @@ -26,7 +26,6 @@ package jdk.nashorn.internal.objects; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; - import java.nio.ByteBuffer; import jdk.nashorn.internal.objects.annotations.Attribute; import jdk.nashorn.internal.objects.annotations.Constructor; @@ -34,6 +33,7 @@ import jdk.nashorn.internal.objects.annotations.Function; import jdk.nashorn.internal.objects.annotations.Getter; import jdk.nashorn.internal.objects.annotations.ScriptClass; import jdk.nashorn.internal.objects.annotations.SpecializedFunction; +import jdk.nashorn.internal.objects.annotations.Where; import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.ScriptObject; @@ -138,6 +138,19 @@ public final class NativeArrayBuffer extends ScriptObject { } /** + * Returns true if an object is an ArrayBufferView + * + * @param self self + * @param obj object to check + * + * @return true if obj is an ArrayBufferView + */ + @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) + public static boolean isView(final Object self, final Object obj) { + return obj instanceof ArrayBufferView; + } + + /** * Slice function * @param self native array buffer * @param begin0 start byte index diff --git a/src/jdk/nashorn/internal/objects/NativeDataView.java b/src/jdk/nashorn/internal/objects/NativeDataView.java index 0adde064..14c11f69 100644 --- a/src/jdk/nashorn/internal/objects/NativeDataView.java +++ b/src/jdk/nashorn/internal/objects/NativeDataView.java @@ -27,6 +27,7 @@ package jdk.nashorn.internal.objects; import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import jdk.nashorn.internal.objects.annotations.Attribute; @@ -104,10 +105,10 @@ public class NativeDataView extends ScriptObject { private NativeDataView(final NativeArrayBuffer arrBuf, final ByteBuffer buf, final int offset, final int length) { super(Global.instance().getDataViewPrototype(), $nasgenmap$); - this.buffer = arrBuf; + this.buffer = arrBuf; this.byteOffset = offset; this.byteLength = length; - this.buf = buf; + this.buf = buf; } /** @@ -134,14 +135,14 @@ public class NativeDataView extends ScriptObject { throw typeError("not.an.arraybuffer.in.dataview"); } - final NativeArrayBuffer arrBuf = (NativeArrayBuffer) args[0]; + final NativeArrayBuffer arrBuf = (NativeArrayBuffer)args[0]; switch (args.length) { - case 1: - return new NativeDataView(arrBuf); - case 2: - return new NativeDataView(arrBuf, JSType.toInt32(args[1])); - default: - return new NativeDataView(arrBuf, JSType.toInt32(args[1]), JSType.toInt32(args[2])); + case 1: + return new NativeDataView(arrBuf); + case 2: + return new NativeDataView(arrBuf, JSType.toInt32(args[1])); + default: + return new NativeDataView(arrBuf, JSType.toInt32(args[1]), JSType.toInt32(args[2])); } } @@ -432,7 +433,7 @@ public class NativeDataView extends ScriptObject { @SpecializedFunction public static long getUint32(final Object self, final int byteOffset) { try { - return JSType.MAX_UINT & getBuffer(self, false).getInt(JSType.toInt32(byteOffset)); + return JSType.toUint32(getBuffer(self, false).getInt(JSType.toInt32(byteOffset))); } catch (final IllegalArgumentException iae) { throw rangeError(iae, "dataview.offset"); } @@ -449,7 +450,7 @@ public class NativeDataView extends ScriptObject { @SpecializedFunction public static long getUint32(final Object self, final int byteOffset, final boolean littleEndian) { try { - return JSType.MAX_UINT & getBuffer(self, littleEndian).getInt(JSType.toInt32(byteOffset)); + return JSType.toUint32(getBuffer(self, littleEndian).getInt(JSType.toInt32(byteOffset))); } catch (final IllegalArgumentException iae) { throw rangeError(iae, "dataview.offset"); } @@ -994,7 +995,7 @@ public class NativeDataView extends ScriptObject { private static NativeDataView checkSelf(final Object self) { if (!(self instanceof NativeDataView)) { - throw typeError("not.an.arraybuffer", ScriptRuntime.safeToString(self)); + throw typeError("not.an.arraybuffer.in.dataview", ScriptRuntime.safeToString(self)); } return (NativeDataView)self; } diff --git a/src/jdk/nashorn/internal/objects/NativeDate.java b/src/jdk/nashorn/internal/objects/NativeDate.java index c85016a5..bb499c26 100644 --- a/src/jdk/nashorn/internal/objects/NativeDate.java +++ b/src/jdk/nashorn/internal/objects/NativeDate.java @@ -1045,7 +1045,8 @@ public final class NativeDate extends ScriptObject { // ECMA 15.9.1.2 TimeWithinDay (t) private static double timeWithinDay(final double t) { - return t % msPerDay; + final double val = t % msPerDay; + return val < 0? val + msPerDay : val; } // ECMA 15.9.1.3 InLeapYear (t) diff --git a/src/jdk/nashorn/internal/objects/NativeDebug.java b/src/jdk/nashorn/internal/objects/NativeDebug.java index 3d8f1095..c42e7843 100644 --- a/src/jdk/nashorn/internal/objects/NativeDebug.java +++ b/src/jdk/nashorn/internal/objects/NativeDebug.java @@ -39,6 +39,7 @@ import jdk.nashorn.internal.runtime.PropertyListeners; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.events.RuntimeEvent; import jdk.nashorn.internal.runtime.linker.LinkerCallSite; import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; @@ -66,6 +67,36 @@ public final class NativeDebug extends ScriptObject { } /** + * Return the ArrayData class for this ScriptObject + * @param self self + * @param obj script object to check + * @return ArrayData class, or undefined if no ArrayData is present + */ + @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) + public static Object getArrayDataClass(final Object self, final Object obj) { + try { + return ((ScriptObject)obj).getArray().getClass(); + } catch (final ClassCastException e) { + return ScriptRuntime.UNDEFINED; + } + } + + /** + * Return the ArrayData for this ScriptObject + * @param self self + * @param obj script object to check + * @return ArrayData, ArrayDatas have toString methods, return Undefined if data missing + */ + @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) + public static Object getArrayData(final Object self, final Object obj) { + try { + return ((ScriptObject)obj).getArray(); + } catch (final ClassCastException e) { + return ScriptRuntime.UNDEFINED; + } + } + + /** * Nashorn extension: get context, context utility * * @param self self reference diff --git a/src/jdk/nashorn/internal/objects/NativeFloat32Array.java b/src/jdk/nashorn/internal/objects/NativeFloat32Array.java index e5ef0b5a..ef7a1ff8 100644 --- a/src/jdk/nashorn/internal/objects/NativeFloat32Array.java +++ b/src/jdk/nashorn/internal/objects/NativeFloat32Array.java @@ -90,6 +90,11 @@ public final class NativeFloat32Array extends ArrayBufferView { } @Override + public Class<?> getBoxedElementType() { + return Double.class; + } + + @Override protected MethodHandle getGetElem() { return GET_ELEM; } diff --git a/src/jdk/nashorn/internal/objects/NativeFloat64Array.java b/src/jdk/nashorn/internal/objects/NativeFloat64Array.java index 1ad61b27..d0413d08 100644 --- a/src/jdk/nashorn/internal/objects/NativeFloat64Array.java +++ b/src/jdk/nashorn/internal/objects/NativeFloat64Array.java @@ -99,6 +99,11 @@ public final class NativeFloat64Array extends ArrayBufferView { return double.class; } + @Override + public Class<?> getBoxedElementType() { + return Double.class; + } + private double getElem(final int index) { try { return nb.get(index); diff --git a/src/jdk/nashorn/internal/objects/NativeFunction.java b/src/jdk/nashorn/internal/objects/NativeFunction.java index c4a79a56..bd2f2dd9 100644 --- a/src/jdk/nashorn/internal/objects/NativeFunction.java +++ b/src/jdk/nashorn/internal/objects/NativeFunction.java @@ -48,6 +48,7 @@ import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; +import jdk.nashorn.internal.runtime.linker.Bootstrap; /** * ECMA 15.3 Function Objects @@ -204,11 +205,7 @@ public final class NativeFunction { * @return function with bound arguments */ @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) - public static ScriptFunction bind(final Object self, final Object... args) { - if (!(self instanceof ScriptFunction)) { - throw typeError("not.a.function", ScriptRuntime.safeToString(self)); - } - + public static Object bind(final Object self, final Object... args) { final Object thiz = (args.length == 0) ? UNDEFINED : args[0]; Object[] arguments; @@ -219,7 +216,7 @@ public final class NativeFunction { arguments = ScriptRuntime.EMPTY_ARRAY; } - return ((ScriptFunctionImpl)self).makeBoundFunction(thiz, arguments); + return Bootstrap.bindCallable(self, thiz, arguments); } /** diff --git a/src/jdk/nashorn/internal/objects/NativeInt16Array.java b/src/jdk/nashorn/internal/objects/NativeInt16Array.java index 06148848..e4cdce51 100644 --- a/src/jdk/nashorn/internal/objects/NativeInt16Array.java +++ b/src/jdk/nashorn/internal/objects/NativeInt16Array.java @@ -100,6 +100,11 @@ public final class NativeInt16Array extends ArrayBufferView { return int.class; } + @Override + public Class<?> getBoxedElementType() { + return Integer.class; + } + private int getElem(final int index) { try { return nb.get(index); diff --git a/src/jdk/nashorn/internal/objects/NativeInt32Array.java b/src/jdk/nashorn/internal/objects/NativeInt32Array.java index 5074dc68..9e664ed7 100644 --- a/src/jdk/nashorn/internal/objects/NativeInt32Array.java +++ b/src/jdk/nashorn/internal/objects/NativeInt32Array.java @@ -118,6 +118,11 @@ public final class NativeInt32Array extends ArrayBufferView { } @Override + public Class<?> getBoxedElementType() { + return Integer.class; + } + + @Override public int getInt(final int index) { return getElem(index); } diff --git a/src/jdk/nashorn/internal/objects/NativeInt8Array.java b/src/jdk/nashorn/internal/objects/NativeInt8Array.java index 319168c0..c336d274 100644 --- a/src/jdk/nashorn/internal/objects/NativeInt8Array.java +++ b/src/jdk/nashorn/internal/objects/NativeInt8Array.java @@ -98,6 +98,11 @@ public final class NativeInt8Array extends ArrayBufferView { return int.class; } + @Override + public Class<?> getBoxedElementType() { + return Integer.class; + } + private int getElem(final int index) { try { return nb.get(index); diff --git a/src/jdk/nashorn/internal/objects/NativeObject.java b/src/jdk/nashorn/internal/objects/NativeObject.java index 5667e6ac..c086442d 100644 --- a/src/jdk/nashorn/internal/objects/NativeObject.java +++ b/src/jdk/nashorn/internal/objects/NativeObject.java @@ -673,7 +673,7 @@ public final class NativeObject { for (final Property prop : properties) { if (prop.isEnumerable()) { final Object value = sourceObj.get(prop.getKey()); - prop.setCurrentType(Object.class); + prop.setType(Object.class); prop.setValue(sourceObj, sourceObj, value, false); propList.add(prop); } @@ -805,7 +805,7 @@ public final class NativeObject { // name and object linked with BeansLinker. (Actually, an even stronger assumption is true: return value is // constant for any given method name and object's class.) return MethodHandles.dropArguments(MethodHandles.constant(Object.class, - Bootstrap.bindDynamicMethod(methodGetter.invoke(source), source)), 0, Object.class); + Bootstrap.bindCallable(methodGetter.invoke(source), source, null)), 0, Object.class); } catch(RuntimeException|Error e) { throw e; } catch(final Throwable t) { diff --git a/src/jdk/nashorn/internal/objects/NativeRegExpExecResult.java b/src/jdk/nashorn/internal/objects/NativeRegExpExecResult.java index ffd6055b..467399ae 100644 --- a/src/jdk/nashorn/internal/objects/NativeRegExpExecResult.java +++ b/src/jdk/nashorn/internal/objects/NativeRegExpExecResult.java @@ -74,7 +74,7 @@ public final class NativeRegExpExecResult extends ScriptObject { @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE) public static Object length(final Object self) { if (self instanceof ScriptObject) { - return ((ScriptObject)self).getArray().length() & JSType.MAX_UINT; + return JSType.toUint32(((ScriptObject)self).getArray().length()); } return 0; diff --git a/src/jdk/nashorn/internal/objects/NativeString.java b/src/jdk/nashorn/internal/objects/NativeString.java index 850af622..7fb86a4a 100644 --- a/src/jdk/nashorn/internal/objects/NativeString.java +++ b/src/jdk/nashorn/internal/objects/NativeString.java @@ -29,6 +29,7 @@ import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; + import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -572,7 +573,7 @@ public final class NativeString extends ScriptObject implements OptimisticBuilti try { return ((CharSequence)self).charAt(pos); } catch (final IndexOutOfBoundsException e) { - throw new ClassCastException(); + throw new ClassCastException(); //invalid char, out of bounds, force relink } } @@ -1380,7 +1381,6 @@ public final class NativeString extends ScriptObject implements OptimisticBuilti * sequence and that we are in range */ private static final class CharCodeAtLinkLogic extends SpecializedFunction.LinkLogic { - private static final CharCodeAtLinkLogic INSTANCE = new CharCodeAtLinkLogic(); @Override @@ -1389,7 +1389,7 @@ public final class NativeString extends ScriptObject implements OptimisticBuilti //check that it's a char sequence or throw cce final CharSequence cs = (CharSequence)self; //check that the index, representable as an int, is inside the array - final int intIndex = JSType.toInteger(request.getArguments()[1]); + final int intIndex = JSType.toInteger(request.getArguments()[2]); return intIndex >= 0 && intIndex < cs.length(); //can link } catch (final ClassCastException | IndexOutOfBoundsException e) { //fallthru diff --git a/src/jdk/nashorn/internal/objects/NativeUint16Array.java b/src/jdk/nashorn/internal/objects/NativeUint16Array.java index 7f8a1923..2fc2c51a 100644 --- a/src/jdk/nashorn/internal/objects/NativeUint16Array.java +++ b/src/jdk/nashorn/internal/objects/NativeUint16Array.java @@ -124,6 +124,11 @@ public final class NativeUint16Array extends ArrayBufferView { } @Override + public Class<?> getBoxedElementType() { + return Integer.class; + } + + @Override public int getInt(final int index) { return getElem(index); } diff --git a/src/jdk/nashorn/internal/objects/NativeUint32Array.java b/src/jdk/nashorn/internal/objects/NativeUint32Array.java index 4772b977..4c83cc01 100644 --- a/src/jdk/nashorn/internal/objects/NativeUint32Array.java +++ b/src/jdk/nashorn/internal/objects/NativeUint32Array.java @@ -105,7 +105,7 @@ public final class NativeUint32Array extends ArrayBufferView { private long getElem(final int index) { try { - return nb.get(index) & JSType.MAX_UINT; + return JSType.toUint32(nb.get(index)); } catch (final IndexOutOfBoundsException e) { throw new ClassCastException(); //force relink - this works for unoptimistic too } @@ -133,6 +133,11 @@ public final class NativeUint32Array extends ArrayBufferView { } @Override + public Class<?> getBoxedElementType() { + return Integer.class; + } + + @Override public int getInt(final int index) { return (int)getLong(index); } diff --git a/src/jdk/nashorn/internal/objects/NativeUint8Array.java b/src/jdk/nashorn/internal/objects/NativeUint8Array.java index be7eb368..6e69ebad 100644 --- a/src/jdk/nashorn/internal/objects/NativeUint8Array.java +++ b/src/jdk/nashorn/internal/objects/NativeUint8Array.java @@ -124,6 +124,11 @@ public final class NativeUint8Array extends ArrayBufferView { } @Override + public Class<?> getBoxedElementType() { + return Integer.class; + } + + @Override public int getInt(final int index) { return getElem(index); } diff --git a/src/jdk/nashorn/internal/objects/NativeUint8ClampedArray.java b/src/jdk/nashorn/internal/objects/NativeUint8ClampedArray.java index 125ac0a2..2dff2612 100644 --- a/src/jdk/nashorn/internal/objects/NativeUint8ClampedArray.java +++ b/src/jdk/nashorn/internal/objects/NativeUint8ClampedArray.java @@ -103,6 +103,11 @@ public final class NativeUint8ClampedArray extends ArrayBufferView { return int.class; } + @Override + public Class<?> getBoxedElementType() { + return int.class; + } + private int getElem(final int index) { try { return nb.get(index) & 0xff; diff --git a/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java b/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java index 36e7716b..f147234b 100644 --- a/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java +++ b/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java @@ -30,7 +30,6 @@ import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; import java.lang.invoke.MethodHandle; import java.util.ArrayList; - import jdk.nashorn.internal.runtime.AccessorProperty; import jdk.nashorn.internal.runtime.GlobalFunctions; import jdk.nashorn.internal.runtime.Property; @@ -237,13 +236,13 @@ public class ScriptFunctionImpl extends ScriptFunction { /** * Same as {@link ScriptFunction#makeBoundFunction(Object, Object[])}. The only reason we override it is so that we - * can expose it to methods in this package. + * can expose it. * @param self the self to bind to this function. Can be null (in which case, null is bound as this). * @param args additional arguments to bind to this function. Can be null or empty to not bind additional arguments. * @return a function with the specified self and parameters bound. */ @Override - protected ScriptFunction makeBoundFunction(final Object self, final Object[] args) { + public ScriptFunction makeBoundFunction(final Object self, final Object[] args) { return super.makeBoundFunction(self, args); } diff --git a/src/jdk/nashorn/internal/objects/annotations/SpecializedFunction.java b/src/jdk/nashorn/internal/objects/annotations/SpecializedFunction.java index 36f47b27..62e6e99f 100644 --- a/src/jdk/nashorn/internal/objects/annotations/SpecializedFunction.java +++ b/src/jdk/nashorn/internal/objects/annotations/SpecializedFunction.java @@ -30,7 +30,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.invoke.MethodHandle; -import java.lang.invoke.SwitchPoint; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.LinkRequest; import jdk.nashorn.internal.runtime.ScriptFunction; @@ -62,10 +61,6 @@ public @interface SpecializedFunction { */ public static final LinkLogic EMPTY_INSTANCE = new Empty(); - private static final SwitchPoint[] INVALIDATED_SWITCHPOINTS = new SwitchPoint[0]; - - private SwitchPoint[] modificationSwitchPoints; //cache - /** Empty link logic class - allow all linking, no guards */ private static final class Empty extends LinkLogic { @Override @@ -167,92 +162,6 @@ public @interface SpecializedFunction { } /** - * Return the modification SwitchPoint of a particular index from this OptimisticBuiltins - * If none exists, one is created and that one is return. - * - * The implementor must map indexes to specific SwitchPoints for specific events and keep - * track of what they mean, for example NativeArray.LENGTH_NOT_WRITABLE_SWITCHPOINT - * might be a useful index mapping - * - * @param index index for SwitchPoint to get or create - * @return modification SwitchPoint of particular index for the receiver - */ - public SwitchPoint getOrCreateModificationSwitchPoint(final int index) { - return null; - } - - /** - * Return the modification SwitchPoint from this OptimisticBuiltins. If none - * exists, one is created and that one is return. - * - * @return modification SwitchPoint for the receiver - */ - public SwitchPoint[] getOrCreateModificationSwitchPoints() { - return null; - } - - /** - * Hook to invalidate a modification SwitchPoint by index. - * - * @param index index for SwitchPoint to invalidate - */ - public void invalidateModificationSwitchPoint(final int index) { - //empty - } - - /** - * Hook to invalidate all modification SwitchPoints for a receiver - */ - public void invalidateModificationSwitchPoints() { - //empty - } - - /** - * Check whether the receiver has an invalidated modification SwitchPoint. - * - * @param index index for the modification SwitchPoint - * @return true if the particular SwitchPoint at the index is invalidated - */ - public boolean hasInvalidatedModificationSwitchPoint(final int index) { - return false; - } - - /** - * Check whether at least one of the modification SwitchPoints has been - * invalidated - * @return true if one of the SwitchPoints has been invalidated - */ - public boolean hasInvalidatedModificationSwitchPoints() { - return false; - } - - /** - * Check whether this OptimisticBuiltins has a SwitchPoints of particular - * index. - * - * As creation overhead for a SwitchPoint is non-zero, we have to create them lazily instead of, - * e.g. in the constructor of every subclass. - * - * @param index index for the modification SwitchPoint - * @return true if a modification SwitchPoint exists, no matter if it has been invalidated or not - */ - public boolean hasModificationSwitchPoint(final int index) { - return false; - } - - /** - * Check whether this OptimisticBuiltins has SwitchPoints. - * - * As creation overhead for a SwitchPoint is non-zero, we have to create them lazily instead of, - * e.g. in the constructor of every subclass. - * - * @return true if a modification SwitchPoint exists, no matter if it has been invalidated or not - */ - public boolean hasModificationSwitchPoints() { - return false; - } - - /** * Check, given a link request and a receiver, if this specialization * fits This is used by the linker in {@link ScriptFunction} to figure * out if an optimistic builtin can be linked when first discovered @@ -265,47 +174,9 @@ public @interface SpecializedFunction { * pick a non specialized target */ public boolean checkLinkable(final Object self, final CallSiteDescriptor desc, final LinkRequest request) { - // no matter what the modification switchpoints are, if any of them are invalidated, - // we can't link. Side effect is that if it's the first time we see this callsite, - // we have to create the SwitchPoint(s) so future modification switchpoint invalidations - // relink it - final SwitchPoint[] sps = getOrCreateModificationSwitchPoints(self); - if (sps == INVALIDATED_SWITCHPOINTS) { - // nope, can't do the fast link as this assumption - // has been invalidated already, e.g. length of an - // array set to not writable - return false; - } - modificationSwitchPoints = sps; //cache - // check the link guard, if it says we can link, go ahead return canLink(self, desc, request); } - - private SwitchPoint[] getOrCreateModificationSwitchPoints(final Object self) { - final SwitchPoint[] sps = getOrCreateModificationSwitchPoints(); //ask for all my switchpoints - if (sps != null) { //switchpoint exists, but some may be invalidated - for (final SwitchPoint sp : sps) { - if (sp.hasBeenInvalidated()) { - return INVALIDATED_SWITCHPOINTS; - } - } - } - return sps; - } - - /** - * Get the cached modification switchpoints. Only possible to do after a link - * check call has been performed, one that has answered "true", or you will get the - * wrong information. - * - * Should be used only from {@link ScriptFunction#findCallMethod} - * - * @return cached modification switchpoints for this callsite, null if none - */ - public SwitchPoint[] getModificationSwitchPoints() { - return modificationSwitchPoints == null ? null : modificationSwitchPoints.clone(); - } } /** diff --git a/src/jdk/nashorn/internal/parser/JSONParser.java b/src/jdk/nashorn/internal/parser/JSONParser.java index a5cae0ed..7faf89a8 100644 --- a/src/jdk/nashorn/internal/parser/JSONParser.java +++ b/src/jdk/nashorn/internal/parser/JSONParser.java @@ -32,7 +32,6 @@ import static jdk.nashorn.internal.parser.TokenType.ESCSTRING; import static jdk.nashorn.internal.parser.TokenType.RBRACE; import static jdk.nashorn.internal.parser.TokenType.RBRACKET; import static jdk.nashorn.internal.parser.TokenType.STRING; - import java.util.ArrayList; import java.util.List; import jdk.nashorn.internal.ir.Expression; diff --git a/src/jdk/nashorn/internal/parser/Parser.java b/src/jdk/nashorn/internal/parser/Parser.java index 3162e184..12ad59e3 100644 --- a/src/jdk/nashorn/internal/parser/Parser.java +++ b/src/jdk/nashorn/internal/parser/Parser.java @@ -559,7 +559,7 @@ loop: // Set up new block. Captures first token. Block newBlock = newBlock(); try { - statement(); + statement(false, false, true); } finally { newBlock = restoreBlock(newBlock); } @@ -707,20 +707,9 @@ loop: FunctionNode.Kind.SCRIPT, functionLine); - // If ES6 block scope is enabled add a per-script block for top-level LET and CONST declarations. - final int startLine = start; - Block outer = useBlockScope() ? newBlock() : null; functionDeclarations = new ArrayList<>(); - - try { - sourceElements(allowPropertyFunction); - addFunctionDeclarations(script); - } finally { - if (outer != null) { - outer = restoreBlock(outer); - appendStatement(new BlockStatement(startLine, outer)); - } - } + sourceElements(allowPropertyFunction); + addFunctionDeclarations(script); functionDeclarations = null; expect(EOF); @@ -783,7 +772,7 @@ loop: try { // Get the next element. - statement(true, allowPropertyFunction); + statement(true, allowPropertyFunction, false); allowPropertyFunction = false; // check for directive prologues @@ -873,13 +862,15 @@ loop: * Parse any of the basic statement types. */ private void statement() { - statement(false, false); + statement(false, false, false); } /** * @param topLevel does this statement occur at the "top level" of a script or a function? + * @param allowPropertyFunction allow property "get" and "set" functions? + * @param singleStatement are we in a single statement context? */ - private void statement(final boolean topLevel, final boolean allowPropertyFunction) { + private void statement(final boolean topLevel, final boolean allowPropertyFunction, final boolean singleStatement) { if (type == FUNCTION) { // As per spec (ECMA section 12), function declarations as arbitrary statement // is not "portable". Implementation can issue a warning or disallow the same. @@ -943,6 +934,9 @@ loop: break; default: if (useBlockScope() && (type == LET || type == CONST)) { + if (singleStatement) { + throw error(AbstractParser.message("expected.stmt", type.getName() + " declaration"), token); + } variableStatement(type, true); break; } @@ -1068,7 +1062,7 @@ loop: next(); final List<VarNode> vars = new ArrayList<>(); - int varFlags = VarNode.IS_STATEMENT; + int varFlags = 0; if (varType == LET) { varFlags |= VarNode.IS_LET; } else if (varType == CONST) { @@ -1221,7 +1215,7 @@ loop: Block outer = useBlockScope() ? newBlock() : null; // Create FOR node, capturing FOR token. - ForNode forNode = new ForNode(line, token, Token.descPosition(token), null, ForNode.IS_FOR); + ForNode forNode = new ForNode(line, token, Token.descPosition(token), null, 0); lc.push(forNode); try { @@ -1241,19 +1235,22 @@ loop: switch (type) { case VAR: - // Var statements captured in for outer block. + // Var declaration captured in for outer block. vars = variableStatement(type, false); break; case SEMICOLON: break; default: if (useBlockScope() && (type == LET || type == CONST)) { - // LET/CONST captured in container block created above. + if (type == LET) { + forNode = forNode.setPerIterationScope(lc); + } + // LET/CONST declaration captured in container block created above. vars = variableStatement(type, false); break; } if (env._const_as_var && type == CONST) { - // Var statements captured in for outer block. + // Var declaration captured in for outer block. vars = variableStatement(TokenType.VAR, false); break; } @@ -1334,11 +1331,12 @@ loop: appendStatement(forNode); } finally { lc.pop(forNode); - if (outer != null) { - outer.setFinish(forNode.getFinish()); - outer = restoreBlock(outer); - appendStatement(new BlockStatement(startLine, outer)); - } + } + + if (outer != null) { + outer.setFinish(forNode.getFinish()); + outer = restoreBlock(outer); + appendStatement(new BlockStatement(startLine, outer)); } } @@ -2646,7 +2644,7 @@ loop: // name is null, generate anonymous name boolean isAnonymous = false; if (name == null) { - final String tmpName = getDefaultValidFunctionName(functionLine); + final String tmpName = getDefaultValidFunctionName(functionLine, isStatement); name = new IdentNode(functionToken, Token.descPosition(functionToken), tmpName); isAnonymous = true; } @@ -2655,7 +2653,15 @@ loop: final List<IdentNode> parameters = formalParameterList(); expect(RPAREN); - FunctionNode functionNode = functionBody(functionToken, name, parameters, FunctionNode.Kind.NORMAL, functionLine); + FunctionNode functionNode; + // Hide the current default name across function boundaries. E.g. "x3 = function x1() { function() {}}" + // If we didn't hide the current default name, then the innermost anonymous function would receive "x3". + hideDefaultName(); + try { + functionNode = functionBody(functionToken, name, parameters, FunctionNode.Kind.NORMAL, functionLine); + } finally { + defaultNames.pop(); + } if (isStatement) { if (topLevel || useBlockScope()) { @@ -2710,11 +2716,7 @@ loop: } if (isStatement) { - int varFlags = VarNode.IS_STATEMENT; - if (!topLevel && useBlockScope()) { - // mark ES6 block functions as lexically scoped - varFlags |= VarNode.IS_LET; - } + final int varFlags = (topLevel || !useBlockScope()) ? 0 : VarNode.IS_LET; final VarNode varNode = new VarNode(functionLine, functionToken, finish, name, functionNode, varFlags); if (topLevel) { functionDeclarations.add(varNode); @@ -2728,9 +2730,17 @@ loop: return functionNode; } - private String getDefaultValidFunctionName(final int functionLine) { + private String getDefaultValidFunctionName(final int functionLine, final boolean isStatement) { final String defaultFunctionName = getDefaultFunctionName(); - return isValidIdentifier(defaultFunctionName) ? defaultFunctionName : ANON_FUNCTION_PREFIX.symbolName() + functionLine; + if (isValidIdentifier(defaultFunctionName)) { + if (isStatement) { + // The name will be used as the LHS of a symbol assignment. We add the anonymous function + // prefix to ensure that it can't clash with another variable. + return ANON_FUNCTION_PREFIX.symbolName() + defaultFunctionName; + } + return defaultFunctionName; + } + return ANON_FUNCTION_PREFIX.symbolName() + functionLine; } private static boolean isValidIdentifier(final String name) { @@ -2764,6 +2774,10 @@ loop: private void markDefaultNameUsed() { defaultNames.pop(); + hideDefaultName(); + } + + private void hideDefaultName() { // Can be any value as long as getDefaultFunctionName doesn't recognize it as something it can extract a value // from. Can't be null defaultNames.push(""); diff --git a/src/jdk/nashorn/internal/runtime/AccessorProperty.java b/src/jdk/nashorn/internal/runtime/AccessorProperty.java index e2935368..a9afeb93 100644 --- a/src/jdk/nashorn/internal/runtime/AccessorProperty.java +++ b/src/jdk/nashorn/internal/runtime/AccessorProperty.java @@ -145,13 +145,6 @@ public class AccessorProperty extends Property { transient MethodHandle objectSetter; /** - * Current type of this object, in object only mode, this is an Object.class. In dual-fields mode - * null means undefined, and primitive types are allowed. The reason a special type is used for - * undefined, is that are no bits left to represent it in primitive types - */ - private Class<?> currentType; - - /** * Delegate constructor for bound properties. This is used for properties created by * {@link ScriptRuntime#mergeScope} and the Nashorn {@code Object.bindProperties} method. * The former is used to add a script's defined globals to the current global scope while @@ -171,7 +164,7 @@ public class AccessorProperty extends Property { this.objectSetter = bindTo(property.objectSetter, delegate); property.GETTER_CACHE = new MethodHandle[NOOF_TYPES]; // Properties created this way are bound to a delegate - setCurrentType(property.getCurrentType()); + setType(property.getType()); } /** @@ -248,7 +241,7 @@ public class AccessorProperty extends Property { objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter; objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter; - setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : getterType); + setType(OBJECT_FIELDS_ONLY ? Object.class : getterType); } /** @@ -317,7 +310,7 @@ public class AccessorProperty extends Property { */ public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot, final Class<?> initialType) { this(key, flags, structure, slot); - setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : initialType); + setType(OBJECT_FIELDS_ONLY ? Object.class : initialType); } /** @@ -330,13 +323,13 @@ public class AccessorProperty extends Property { protected AccessorProperty(final AccessorProperty property, final Class<?> newType) { super(property, property.getFlags()); - this.GETTER_CACHE = newType != property.getCurrentType() ? new MethodHandle[NOOF_TYPES] : property.GETTER_CACHE; + this.GETTER_CACHE = newType != property.getLocalType() ? new MethodHandle[NOOF_TYPES] : property.GETTER_CACHE; this.primitiveGetter = property.primitiveGetter; this.primitiveSetter = property.primitiveSetter; this.objectGetter = property.objectGetter; this.objectSetter = property.objectSetter; - setCurrentType(newType); + setType(newType); } /** @@ -345,7 +338,7 @@ public class AccessorProperty extends Property { * @param property source property */ protected AccessorProperty(final AccessorProperty property) { - this(property, property.getCurrentType()); + this(property, property.getLocalType()); } /** @@ -354,7 +347,7 @@ public class AccessorProperty extends Property { * @param initialValue initial value */ protected final void setInitialValue(final ScriptObject owner, final Object initialValue) { - setCurrentType(JSType.unboxedFieldType(initialValue)); + setType(JSType.unboxedFieldType(initialValue)); if (initialValue instanceof Integer) { invokeSetter(owner, ((Integer)initialValue).intValue()); } else if (initialValue instanceof Long) { @@ -370,7 +363,7 @@ public class AccessorProperty extends Property { * Initialize the type of a property */ protected final void initializeType() { - setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : null); + setType(OBJECT_FIELDS_ONLY ? Object.class : null); } private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException { @@ -557,12 +550,12 @@ public class AccessorProperty extends Property { } else { getter = debug( createGetter( - getCurrentType(), + getLocalType(), type, primitiveGetter, objectGetter, INVALID_PROGRAM_POINT), - getCurrentType(), + getLocalType(), type, "get"); getterCache[i] = getter; @@ -582,18 +575,18 @@ public class AccessorProperty extends Property { return debug( createGetter( - getCurrentType(), + getLocalType(), type, primitiveGetter, objectGetter, programPoint), - getCurrentType(), + getLocalType(), type, "get"); } private MethodHandle getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint) { - final MethodHandle g = getGetter(getCurrentType()); + final MethodHandle g = getGetter(getLocalType()); return MH.asType(OptimisticReturnFilters.filterOptimisticReturnValue(g, type, programPoint), g.type().changeReturnType(type)); } @@ -631,7 +624,7 @@ public class AccessorProperty extends Property { } private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) { - return debug(createSetter(forType, type, primitiveSetter, objectSetter), getCurrentType(), type, "set"); + return debug(createSetter(forType, type, primitiveSetter, objectSetter), getLocalType(), type, "set"); } /** @@ -639,7 +632,7 @@ public class AccessorProperty extends Property { * @return true if undefined */ protected final boolean isUndefined() { - return getCurrentType() == null; + return getLocalType() == null; } @Override @@ -647,7 +640,7 @@ public class AccessorProperty extends Property { checkUndeclared(); final int typeIndex = getAccessorTypeIndex(type); - final int currentTypeIndex = getAccessorTypeIndex(getCurrentType()); + final int currentTypeIndex = getAccessorTypeIndex(getLocalType()); //if we are asking for an object setter, but are still a primitive type, we might try to box it MethodHandle mh; @@ -656,13 +649,13 @@ public class AccessorProperty extends Property { final PropertyMap newMap = getWiderMap(currentMap, newProperty); final MethodHandle widerSetter = newProperty.getSetter(type, newMap); - final Class<?> ct = getCurrentType(); + final Class<?> ct = getLocalType(); mh = MH.filterArguments(widerSetter, 0, MH.insertArguments(debugReplace(ct, type, currentMap, newMap) , 1, newMap)); if (ct != null && ct.isPrimitive() && !type.isPrimitive()) { mh = ObjectClassGenerator.createGuardBoxedPrimitiveSetter(ct, generateSetter(ct, ct), mh); } } else { - final Class<?> forType = isUndefined() ? type : getCurrentType(); + final Class<?> forType = isUndefined() ? type : getLocalType(); mh = generateSetter(!forType.isPrimitive() ? Object.class : forType, type); } @@ -681,24 +674,13 @@ public class AccessorProperty extends Property { return false; } // Return true for currently undefined even if non-writable/configurable to allow initialization of ES6 CONST. - return getCurrentType() == null || (getCurrentType() != Object.class && (isConfigurable() || isWritable())); + return getLocalType() == null || (getLocalType() != Object.class && (isConfigurable() || isWritable())); } private boolean needsInvalidator(final int typeIndex, final int currentTypeIndex) { return canChangeType() && typeIndex > currentTypeIndex; } - @Override - public final void setCurrentType(final Class<?> currentType) { - assert currentType != boolean.class : "no boolean storage support yet - fix this"; - this.currentType = currentType == null ? null : currentType.isPrimitive() ? currentType : Object.class; - } - - @Override - public Class<?> getCurrentType() { - return currentType; - } - private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) { if (!Context.DEBUG || !Global.hasInstance()) { return mh; diff --git a/src/jdk/nashorn/internal/runtime/CodeStore.java b/src/jdk/nashorn/internal/runtime/CodeStore.java index 0748ccc3..4e745ff4 100644 --- a/src/jdk/nashorn/internal/runtime/CodeStore.java +++ b/src/jdk/nashorn/internal/runtime/CodeStore.java @@ -41,6 +41,7 @@ import java.security.PrivilegedExceptionAction; import java.util.Iterator; import java.util.Map; import java.util.ServiceLoader; +import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; @@ -81,10 +82,9 @@ public abstract class CodeStore implements Loggable { * Returns a new code store instance. * * @param context the current context - * @return The instance - * @throws IOException If an error occurs + * @return The instance, or null if code store could not be created */ - public static CodeStore newCodeStore(final Context context) throws IOException { + public static CodeStore newCodeStore(final Context context) { final Class<CodeStore> baseClass = CodeStore.class; try { // security check first @@ -102,9 +102,14 @@ public abstract class CodeStore implements Loggable { } catch (final AccessControlException e) { context.getLogger(CodeStore.class).warning("failed to load code store provider ", e); } - final CodeStore store = new DirectoryCodeStore(); - store.initLogger(context); - return store; + try { + final CodeStore store = new DirectoryCodeStore(context); + store.initLogger(context); + return store; + } catch (final IOException e) { + context.getLogger(CodeStore.class).warning("failed to create cache directory ", e); + return null; + } } @@ -210,32 +215,34 @@ public abstract class CodeStore implements Loggable { /** * Constructor * + * @param context the current context * @throws IOException if there are read/write problems with the cache and cache directory */ - public DirectoryCodeStore() throws IOException { - this(Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE); + public DirectoryCodeStore(final Context context) throws IOException { + this(context, Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE); } /** * Constructor * + * @param context the current context * @param path directory to store code in * @param readOnly is this a read only code store * @param minSize minimum file size for caching scripts * @throws IOException if there are read/write problems with the cache and cache directory */ - public DirectoryCodeStore(final String path, final boolean readOnly, final int minSize) throws IOException { - this.dir = checkDirectory(path, readOnly); + public DirectoryCodeStore(final Context context, final String path, final boolean readOnly, final int minSize) throws IOException { + this.dir = checkDirectory(path, context.getEnv(), readOnly); this.readOnly = readOnly; this.minSize = minSize; } - private static File checkDirectory(final String path, final boolean readOnly) throws IOException { + private static File checkDirectory(final String path, final ScriptEnvironment env, final boolean readOnly) throws IOException { try { return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { @Override public File run() throws IOException { - final File dir = new File(path).getAbsoluteFile(); + final File dir = new File(path, getVersionDir(env)).getAbsoluteFile(); if (readOnly) { if (!dir.exists() || !dir.isDirectory()) { throw new IOException("Not a directory: " + dir.getPath()); @@ -257,6 +264,15 @@ public abstract class CodeStore implements Loggable { } } + private static String getVersionDir(final ScriptEnvironment env) throws IOException { + try { + final String versionDir = OptimisticTypesPersistence.getVersionDirName(); + return env._optimistic_types ? versionDir + "_opt" : versionDir; + } catch (final Exception e) { + throw new IOException(e); + } + } + @Override public StoredScript load(final Source source, final String functionKey) { if (source.getLength() < minSize) { diff --git a/src/jdk/nashorn/internal/runtime/CompiledFunction.java b/src/jdk/nashorn/internal/runtime/CompiledFunction.java index b37f94fa..ce20ed31 100644 --- a/src/jdk/nashorn/internal/runtime/CompiledFunction.java +++ b/src/jdk/nashorn/internal/runtime/CompiledFunction.java @@ -27,16 +27,17 @@ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; - import java.lang.invoke.CallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.MutableCallSite; import java.lang.invoke.SwitchPoint; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.function.Supplier; @@ -727,34 +728,58 @@ final class CompiledFunction { * @param ipp * @return string describing the ipp map */ - private static String toStringInvalidations(final Map<Integer, Type> ipp) { + private static List<String> toStringInvalidations(final Map<Integer, Type> ipp) { if (ipp == null) { - return ""; + return Collections.emptyList(); } - final StringBuilder sb = new StringBuilder(); + final List<String> list = new ArrayList<>(); for (final Iterator<Map.Entry<Integer, Type>> iter = ipp.entrySet().iterator(); iter.hasNext(); ) { final Map.Entry<Integer, Type> entry = iter.next(); final char bct = entry.getValue().getBytecodeStackType(); + final String type; + + switch (entry.getValue().getBytecodeStackType()) { + case 'A': + type = "object"; + break; + case 'I': + type = "int"; + break; + case 'J': + type = "long"; + break; + case 'D': + type = "double"; + break; + default: + type = String.valueOf(bct); + break; + } + final StringBuilder sb = new StringBuilder(); sb.append('['). + append("program point: "). append(entry.getKey()). - append("->"). - append(bct == 'A' ? 'O' : bct). + append(" -> "). + append(type). append(']'); - if (iter.hasNext()) { - sb.append(' '); - } + list.add(sb.toString()); } - return sb.toString(); + return list; } private void logRecompile(final String reason, final FunctionNode fn, final MethodType type, final Map<Integer, Type> ipp) { if (log.isEnabled()) { - log.info(reason, DebugLogger.quote(fn.getName()), " signature: ", type, " ", toStringInvalidations(ipp)); + log.info(reason, DebugLogger.quote(fn.getName()), " signature: ", type); + log.indent(); + for (final String str : toStringInvalidations(ipp)) { + log.fine(str); + } + log.unindent(); } } @@ -770,14 +795,21 @@ final class CompiledFunction { */ private synchronized MethodHandle handleRewriteException(final OptimismInfo oldOptInfo, final RewriteException re) { if (log.isEnabled()) { - log.info(new RecompilationEvent(Level.INFO, re, re.getReturnValueNonDestructive()), "RewriteException ", re.getMessageShort()); + log.info( + new RecompilationEvent( + Level.INFO, + re, + re.getReturnValueNonDestructive()), + "caught RewriteException ", + re.getMessageShort()); + log.indent(); } final MethodType type = type(); // Compiler needs a call site type as its input, which always has a callee parameter, so we must add it if // this function doesn't have a callee parameter. - final MethodType callSiteType = type.parameterType(0) == ScriptFunction.class ? + final MethodType ct = type.parameterType(0) == ScriptFunction.class ? type : type.insertParameterTypes(0, ScriptFunction.class); final OptimismInfo currentOptInfo = optimismInfo; @@ -788,43 +820,43 @@ final class CompiledFunction { final OptimismInfo effectiveOptInfo = currentOptInfo != null ? currentOptInfo : oldOptInfo; FunctionNode fn = effectiveOptInfo.reparse(); final boolean serialized = effectiveOptInfo.isSerialized(); - final Compiler compiler = effectiveOptInfo.getCompiler(fn, callSiteType, re); //set to non rest-of + final Compiler compiler = effectiveOptInfo.getCompiler(fn, ct, re); //set to non rest-of if (!shouldRecompile) { // It didn't necessarily recompile, e.g. for an outer invocation of a recursive function if we already // recompiled a deoptimized version for an inner invocation. // We still need to do the rest of from the beginning - logRecompile("Rest-of compilation [STANDALONE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints); + logRecompile("Rest-of compilation [STANDALONE] ", fn, ct, effectiveOptInfo.invalidatedProgramPoints); return restOfHandle(effectiveOptInfo, compiler.compile(fn, serialized ? CompilationPhases.COMPILE_SERIALIZED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null); } - logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints); + logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, ct, effectiveOptInfo.invalidatedProgramPoints); fn = compiler.compile(fn, serialized ? CompilationPhases.RECOMPILE_SERIALIZED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE); - log.info("Reusable IR generated"); + log.fine("Reusable IR generated"); // compile the rest of the function, and install it log.info("Generating and installing bytecode from reusable IR..."); - logRecompile("Rest-of compilation [CODE PIPELINE REUSE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints); + logRecompile("Rest-of compilation [CODE PIPELINE REUSE] ", fn, ct, effectiveOptInfo.invalidatedProgramPoints); final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL); if (effectiveOptInfo.data.usePersistentCodeCache()) { final RecompilableScriptFunctionData data = effectiveOptInfo.data; final int functionNodeId = data.getFunctionNodeId(); - final TypeMap typeMap = data.typeMap(callSiteType); + final TypeMap typeMap = data.typeMap(ct); final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId); final String cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes); compiler.persistClassInfo(cacheKey, normalFn); } - log.info("Done."); - final boolean canBeDeoptimized = normalFn.canBeDeoptimized(); if (log.isEnabled()) { - log.info("Recompiled '", fn.getName(), "' (", Debug.id(this), ") ", canBeDeoptimized ? " can still be deoptimized." : " is completely deoptimized."); - } + log.unindent(); + log.info("Done."); - log.info("Looking up invoker..."); + log.info("Recompiled '", fn.getName(), "' (", Debug.id(this), ") ", canBeDeoptimized ? "can still be deoptimized." : " is completely deoptimized."); + log.finest("Looking up invoker..."); + } final MethodHandle newInvoker = effectiveOptInfo.data.lookup(fn); invoker = newInvoker.asType(type.changeReturnType(newInvoker.type().returnType())); diff --git a/src/jdk/nashorn/internal/runtime/Context.java b/src/jdk/nashorn/internal/runtime/Context.java index 23f0e3ab..9e851998 100644 --- a/src/jdk/nashorn/internal/runtime/Context.java +++ b/src/jdk/nashorn/internal/runtime/Context.java @@ -33,6 +33,7 @@ import static jdk.nashorn.internal.runtime.CodeStore.newCodeStore; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; import static jdk.nashorn.internal.runtime.Source.sourceFor; + import java.io.File; import java.io.IOException; import java.io.PrintWriter; @@ -60,6 +61,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; @@ -262,6 +264,10 @@ public final class Context { // persistent code store private CodeStore codeStore; + // A factory for linking global properties as constant method handles. It is created when the first Global + // is created, and invalidated forever once the second global is created. + private final AtomicReference<GlobalConstants> globalConstantsRef = new AtomicReference<>(); + /** * Get the current global scope * @return the current global scope @@ -293,7 +299,10 @@ public final class Context { assert getGlobal() != global; //same code can be cached between globals, then we need to invalidate method handle constants if (global != null) { - Global.getConstants().invalidateAll(); + final GlobalConstants globalConstants = getContext(global).getGlobalConstants(); + if (globalConstants != null) { + globalConstants.invalidateAll(); + } } currentGlobal.set(global); } @@ -500,11 +509,7 @@ public final class Context { } if (env._persistent_cache) { - try { - codeStore = newCodeStore(this); - } catch (final IOException e) { - throw new RuntimeException("Error initializing code cache", e); - } + codeStore = newCodeStore(this); } // print version info if asked. @@ -529,6 +534,15 @@ public final class Context { } /** + * Returns the factory for constant method handles for global properties. The returned factory can be + * invalidated if this Context has more than one Global. + * @return the factory for constant method handles for global properties. + */ + GlobalConstants getGlobalConstants() { + return globalConstantsRef.get(); + } + + /** * Get the error manager for this context * @return error manger */ @@ -1016,9 +1030,32 @@ public final class Context { * @return the global script object */ public Global newGlobal() { + createOrInvalidateGlobalConstants(); return new Global(this); } + private void createOrInvalidateGlobalConstants() { + for (;;) { + final GlobalConstants currentGlobalConstants = getGlobalConstants(); + if (currentGlobalConstants != null) { + // Subsequent invocation; we're creating our second or later Global. GlobalConstants is not safe to use + // with more than one Global, as the constant method handle linkages it creates create a coupling + // between the Global and the call sites in the compiled code. + currentGlobalConstants.invalidateForever(); + return; + } + final GlobalConstants newGlobalConstants = new GlobalConstants(getLogger(GlobalConstants.class)); + if (globalConstantsRef.compareAndSet(null, newGlobalConstants)) { + // First invocation; we're creating the first Global in this Context. Create the GlobalConstants object + // for this Context. + return; + } + + // If we reach here, then we started out as the first invocation, but another concurrent invocation won the + // CAS race. We'll just let the loop repeat and invalidate the CAS race winner. + } + } + /** * Initialize given global scope object. * @@ -1057,12 +1094,19 @@ public final class Context { * @return current global's context */ static Context getContextTrusted() { - return ((ScriptObject)Context.getGlobal()).getContext(); + return getContext(getGlobal()); } static Context getContextTrustedOrNull() { final Global global = Context.getGlobal(); - return global == null ? null : ((ScriptObject)global).getContext(); + return global == null ? null : getContext(global); + } + + private static Context getContext(final Global global) { + // We can't invoke Global.getContext() directly, as it's a protected override, and Global isn't in our package. + // In order to access the method, we must cast it to ScriptObject first (which is in our package) and then let + // virtual invocation do its thing. + return ((ScriptObject)global).getContext(); } /** @@ -1150,10 +1194,9 @@ public final class Context { StoredScript storedScript = null; FunctionNode functionNode = null; - // We only use the code store here if optimistic types are disabled. With optimistic types, - // code is stored per function in RecompilableScriptFunctionData. - // TODO: This should really be triggered by lazy compilation, not optimistic types. - final boolean useCodeStore = env._persistent_cache && !env._parse_only && !env._optimistic_types; + // We only use the code store here if optimistic types are disabled. With optimistic types, initial compilation + // just creates a thin wrapper, and actual code is stored per function in RecompilableScriptFunctionData. + final boolean useCodeStore = codeStore != null && !env._parse_only && !env._optimistic_types; final String cacheKey = useCodeStore ? CodeStore.getCacheKey(0, null) : null; if (useCodeStore) { diff --git a/src/jdk/nashorn/internal/runtime/ECMAException.java b/src/jdk/nashorn/internal/runtime/ECMAException.java index 954a1707..f906e18f 100644 --- a/src/jdk/nashorn/internal/runtime/ECMAException.java +++ b/src/jdk/nashorn/internal/runtime/ECMAException.java @@ -96,15 +96,17 @@ public final class ECMAException extends NashornException { // If thrown object is an Error or sub-object like TypeError, then // an ECMAException object has been already initialized at constructor. if (thrown instanceof ScriptObject) { - final ScriptObject sobj = (ScriptObject)thrown; - final Object exception = getException(sobj); + final Object exception = getException((ScriptObject)thrown); if (exception instanceof ECMAException) { - // copy over file name, line number and column number. final ECMAException ee = (ECMAException)exception; - ee.setFileName(fileName); - ee.setLineNumber(line); - ee.setColumnNumber(column); - return ee; + // Make sure exception has correct thrown reference because that's what will end up getting caught. + if (ee.getThrown() == thrown) { + // copy over file name, line number and column number. + ee.setFileName(fileName); + ee.setLineNumber(line); + ee.setColumnNumber(column); + return ee; + } } } @@ -154,7 +156,11 @@ public final class ECMAException extends NashornException { * @return a {@link ECMAException} */ public static Object getException(final ScriptObject errObj) { - return errObj.get(ECMAException.EXCEPTION_PROPERTY); + // Exclude inherited properties that may belong to errors in the prototype chain. + if (errObj.hasOwnProperty(ECMAException.EXCEPTION_PROPERTY)) { + return errObj.get(ECMAException.EXCEPTION_PROPERTY); + } + return null; } /** diff --git a/src/jdk/nashorn/internal/runtime/FindProperty.java b/src/jdk/nashorn/internal/runtime/FindProperty.java index b4e00124..3b153c58 100644 --- a/src/jdk/nashorn/internal/runtime/FindProperty.java +++ b/src/jdk/nashorn/internal/runtime/FindProperty.java @@ -84,13 +84,18 @@ public final class FindProperty { * @return method handle for the getter */ public MethodHandle getGetter(final Class<?> type, final int programPoint, final LinkRequest request) { - final MethodHandle getter; + MethodHandle getter; if (isValid(programPoint)) { getter = property.getOptimisticGetter(type, programPoint); } else { getter = property.getGetter(type); } if (property instanceof UserAccessorProperty) { + getter = MH.insertArguments(getter, 1, UserAccessorProperty.getINVOKE_UA_GETTER(type, programPoint)); + if (isValid(programPoint) && type.isPrimitive()) { + getter = MH.insertArguments(getter, 1, programPoint); + } + property.setType(type); return insertAccessorsGetter((UserAccessorProperty) property, request, getter); } return getter; @@ -111,7 +116,8 @@ public final class FindProperty { public MethodHandle getSetter(final Class<?> type, final boolean strict, final LinkRequest request) { MethodHandle setter = property.getSetter(type, getOwner().getMap()); if (property instanceof UserAccessorProperty) { - setter = MH.insertArguments(setter, 1, strict ? property.getKey() : null); + setter = MH.insertArguments(setter, 1, UserAccessorProperty.getINVOKE_UA_SETTER(type), strict ? property.getKey() : null); + property.setType(type); return insertAccessorsGetter((UserAccessorProperty) property, request, setter); } diff --git a/src/jdk/nashorn/internal/runtime/GlobalConstants.java b/src/jdk/nashorn/internal/runtime/GlobalConstants.java index c6f9b964..cd1061c2 100644 --- a/src/jdk/nashorn/internal/runtime/GlobalConstants.java +++ b/src/jdk/nashorn/internal/runtime/GlobalConstants.java @@ -31,12 +31,14 @@ import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.getProgramPoint; import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote; + import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.SwitchPoint; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.DynamicLinker; @@ -50,7 +52,7 @@ import jdk.nashorn.internal.runtime.logging.Loggable; import jdk.nashorn.internal.runtime.logging.Logger; /** - * Each global owns one of these. This is basically table of accessors + * Each context owns one of these. This is basically table of accessors * for global properties. A global constant is evaluated to a MethodHandle.constant * for faster access and to avoid walking to proto chain looking for it. * @@ -67,12 +69,19 @@ import jdk.nashorn.internal.runtime.logging.Logger; * reregister the switchpoint. Set twice or more - don't try again forever, or we'd * just end up relinking our way into megamorphisism. * + * Also it has to be noted that this kind of linking creates a coupling between a Global + * and the call sites in compiled code belonging to the Context. For this reason, the + * linkage becomes incorrect as soon as the Context has more than one Global. The + * {@link #invalidateForever()} is invoked by the Context to invalidate all linkages and + * turn off the functionality of this object as soon as the Context's {@link Context#newGlobal()} is invoked + * for second time. + * * We can extend this to ScriptObjects in general (GLOBAL_ONLY=false), which requires * a receiver guard on the constant getter, but it currently leaks memory and its benefits * have not yet been investigated property. * - * As long as all Globals share the same constant instance, we need synchronization - * whenever we access the instance. + * As long as all Globals in a Context share the same GlobalConstants instance, we need synchronization + * whenever we access it. */ @Logger(name="const") public final class GlobalConstants implements Loggable { @@ -82,7 +91,7 @@ public final class GlobalConstants implements Loggable { * Script objects require a receiver guard, which is memory intensive, so this is currently * disabled. We might implement a weak reference based approach to this later. */ - private static final boolean GLOBAL_ONLY = true; + public static final boolean GLOBAL_ONLY = true; private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); @@ -98,6 +107,8 @@ public final class GlobalConstants implements Loggable { */ private final Map<String, Access> map = new HashMap<>(); + private final AtomicBoolean invalidatedForever = new AtomicBoolean(false); + /** * Constructor - used only by global * @param log logger, or null if none @@ -216,10 +227,34 @@ public final class GlobalConstants implements Loggable { * the same class for a new global, but the builtins and global scoped variables * will have changed. */ - public synchronized void invalidateAll() { - log.info("New global created - invalidating all constant callsites without increasing invocation count."); - for (final Access acc : map.values()) { - acc.invalidateUncounted(); + public void invalidateAll() { + if (!invalidatedForever.get()) { + log.info("New global created - invalidating all constant callsites without increasing invocation count."); + synchronized (this) { + for (final Access acc : map.values()) { + acc.invalidateUncounted(); + } + } + } + } + + /** + * To avoid an expensive global guard "is this the same global", similar to the + * receiver guard on the ScriptObject level, we invalidate all getters when the + * second Global is created by the Context owning this instance. After this + * method is invoked, this GlobalConstants instance will both invalidate all the + * switch points it produced, and it will stop handing out new method handles + * altogether. + */ + public void invalidateForever() { + if (invalidatedForever.compareAndSet(false, true)) { + log.info("New global created - invalidating all constant callsites."); + synchronized (this) { + for (final Access acc : map.values()) { + acc.invalidateForever(); + } + map.clear(); + } } } @@ -251,7 +286,7 @@ public final class GlobalConstants implements Loggable { return obj; } - private synchronized Access getOrCreateSwitchPoint(final String name) { + private Access getOrCreateSwitchPoint(final String name) { Access acc = map.get(name); if (acc != null) { return acc; @@ -267,9 +302,13 @@ public final class GlobalConstants implements Loggable { * @param name name of property */ void delete(final String name) { - final Access acc = map.get(name); - if (acc != null) { - acc.invalidateForever(); + if (!invalidatedForever.get()) { + synchronized (this) { + final Access acc = map.get(name); + if (acc != null) { + acc.invalidateForever(); + } + } } } @@ -313,45 +352,45 @@ public final class GlobalConstants implements Loggable { * * @return null if failed to set up constant linkage */ - synchronized GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) { - if (GLOBAL_ONLY && !isGlobalSetter(receiver, find)) { + GuardedInvocation findSetMethod(final FindProperty find, final ScriptObject receiver, final GuardedInvocation inv, final CallSiteDescriptor desc, final LinkRequest request) { + if (invalidatedForever.get() || (GLOBAL_ONLY && !isGlobalSetter(receiver, find))) { return null; } final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); - final Access acc = getOrCreateSwitchPoint(name); - - if (log.isEnabled()) { - log.fine("Trying to link constant SETTER ", acc); - } + synchronized (this) { + final Access acc = getOrCreateSwitchPoint(name); - if (!acc.mayRetry()) { if (log.isEnabled()) { - log.fine("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); + log.fine("Trying to link constant SETTER ", acc); } - return null; - } - assert acc.mayRetry(); + if (!acc.mayRetry() || invalidatedForever.get()) { + if (log.isEnabled()) { + log.fine("*** SET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); + } + return null; + } - if (acc.hasBeenInvalidated()) { - log.info("New chance for " + acc); - acc.newSwitchPoint(); - } + if (acc.hasBeenInvalidated()) { + log.info("New chance for " + acc); + acc.newSwitchPoint(); + } - assert !acc.hasBeenInvalidated(); + assert !acc.hasBeenInvalidated(); - // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter - final MethodHandle target = inv.getInvocation(); - final Class<?> receiverType = target.type().parameterType(0); - final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP, this); - final MethodHandle invalidator = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType)); - final MethodHandle mh = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc)); + // if we haven't given up on this symbol, add a switchpoint invalidation filter to the receiver parameter + final MethodHandle target = inv.getInvocation(); + final Class<?> receiverType = target.type().parameterType(0); + final MethodHandle boundInvalidator = MH.bindTo(INVALIDATE_SP, this); + final MethodHandle invalidator = MH.asType(boundInvalidator, boundInvalidator.type().changeParameterType(0, receiverType).changeReturnType(receiverType)); + final MethodHandle mh = MH.filterArguments(inv.getInvocation(), 0, MH.insertArguments(invalidator, 1, acc)); - assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints()); - log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint()); - return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException()); + assert inv.getSwitchPoints() == null : Arrays.asList(inv.getSwitchPoints()); + log.info("Linked setter " + quote(name) + " " + acc.getSwitchPoint()); + return new GuardedInvocation(mh, inv.getGuard(), acc.getSwitchPoint(), inv.getException()); + } } /** @@ -380,11 +419,11 @@ public final class GlobalConstants implements Loggable { * * @return resulting getter, or null if failed to create constant */ - synchronized GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) { + GuardedInvocation findGetMethod(final FindProperty find, final ScriptObject receiver, final CallSiteDescriptor desc) { // Only use constant getter for fast scope access, because the receiver may change between invocations // for slow-scope and non-scope callsites. // Also return null for user accessor properties as they may have side effects. - if (!NashornCallSiteDescriptor.isFastScope(desc) + if (invalidatedForever.get() || !NashornCallSiteDescriptor.isFastScope(desc) || (GLOBAL_ONLY && !find.getOwner().isGlobal()) || find.getProperty() instanceof UserAccessorProperty) { return null; @@ -395,51 +434,53 @@ public final class GlobalConstants implements Loggable { final Class<?> retType = desc.getMethodType().returnType(); final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); - final Access acc = getOrCreateSwitchPoint(name); - - log.fine("Starting to look up object value " + name); - final Object c = find.getObjectValue(); + synchronized (this) { + final Access acc = getOrCreateSwitchPoint(name); - if (log.isEnabled()) { - log.fine("Trying to link constant GETTER " + acc + " value = " + c); - } + log.fine("Starting to look up object value " + name); + final Object c = find.getObjectValue(); - if (acc.hasBeenInvalidated() || acc.guardFailed()) { if (log.isEnabled()) { - log.info("*** GET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); + log.fine("Trying to link constant GETTER " + acc + " value = " + c); } - return null; - } - final MethodHandle cmh = constantGetter(c); + if (acc.hasBeenInvalidated() || acc.guardFailed() || invalidatedForever.get()) { + if (log.isEnabled()) { + log.info("*** GET: Giving up on " + quote(name) + " - retry count has exceeded " + DynamicLinker.getLinkedCallSiteLocation()); + } + return null; + } - MethodHandle mh; - MethodHandle guard; + final MethodHandle cmh = constantGetter(c); - if (isOptimistic) { - if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) { - //widen return type - this is pessimistic, so it will always work - mh = MH.asType(cmh, cmh.type().changeReturnType(retType)); + MethodHandle mh; + MethodHandle guard; + + if (isOptimistic) { + if (JSType.getAccessorTypeIndex(cmh.type().returnType()) <= JSType.getAccessorTypeIndex(retType)) { + //widen return type - this is pessimistic, so it will always work + mh = MH.asType(cmh, cmh.type().changeReturnType(retType)); + } else { + //immediately invalidate - we asked for a too wide constant as a narrower one + mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class); + } } else { - //immediately invalidate - we asked for a too wide constant as a narrower one - mh = MH.dropArguments(MH.insertArguments(JSType.THROW_UNWARRANTED.methodHandle(), 0, c, programPoint), 0, Object.class); + //pessimistic return type filter + mh = Lookup.filterReturnType(cmh, retType); } - } else { - //pessimistic return type filter - mh = Lookup.filterReturnType(cmh, retType); - } - if (find.getOwner().isGlobal()) { - guard = null; - } else { - guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver); - } + if (find.getOwner().isGlobal()) { + guard = null; + } else { + guard = MH.insertArguments(RECEIVER_GUARD, 0, acc, receiver); + } - if (log.isEnabled()) { - log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint()); - mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc); - } + if (log.isEnabled()) { + log.info("Linked getter " + quote(name) + " as MethodHandle.constant() -> " + c + " " + acc.getSwitchPoint()); + mh = MethodHandleFactory.addDebugPrintout(log, Level.FINE, mh, "get const " + acc); + } - return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null); + return new GuardedInvocation(mh, guard, acc.getSwitchPoint(), null); + } } } diff --git a/src/jdk/nashorn/internal/runtime/JSType.java b/src/jdk/nashorn/internal/runtime/JSType.java index b040b588..7e54b1e2 100644 --- a/src/jdk/nashorn/internal/runtime/JSType.java +++ b/src/jdk/nashorn/internal/runtime/JSType.java @@ -29,7 +29,6 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY; import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; - import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Array; @@ -115,6 +114,9 @@ public enum JSType { /** JavaScript compliant conversion function from double to int32 */ public static final Call TO_INT32_D = staticCall(JSTYPE_LOOKUP, JSType.class, "toInt32", int.class, double.class); + /** JavaScript compliant conversion function from int to uint32 */ + public static final Call TO_UINT32_I = staticCall(JSTYPE_LOOKUP, JSType.class, "toUint32", long.class, int.class); + /** JavaScript compliant conversion function from Object to uint32 */ public static final Call TO_UINT32 = staticCall(JSTYPE_LOOKUP, JSType.class, "toUint32", long.class, Object.class); @@ -148,6 +150,12 @@ public enum JSType { /** Div exact wrapper for potentially integer division that turns into float point */ public static final Call DIV_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "divExact", int.class, int.class, int.class, int.class); + /** Div zero wrapper for integer division that handles (0/0)|0 == 0 */ + public static final Call DIV_ZERO = staticCall(JSTYPE_LOOKUP, JSType.class, "divZero", int.class, int.class, int.class); + + /** Mod zero wrapper for integer division that handles (0%0)|0 == 0 */ + public static final Call REM_ZERO = staticCall(JSTYPE_LOOKUP, JSType.class, "remZero", int.class, int.class, int.class); + /** Mod exact wrapper for potentially integer remainders that turns into float point */ public static final Call REM_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "remExact", int.class, int.class, int.class, int.class); @@ -172,6 +180,12 @@ public enum JSType { /** Div exact wrapper for potentially integer division that turns into float point */ public static final Call DIV_EXACT_LONG = staticCall(JSTYPE_LOOKUP, JSType.class, "divExact", long.class, long.class, long.class, int.class); + /** Div zero wrapper for long division that handles (0/0) >>> 0 == 0 */ + public static final Call DIV_ZERO_LONG = staticCall(JSTYPE_LOOKUP, JSType.class, "divZero", long.class, long.class, long.class); + + /** Mod zero wrapper for long division that handles (0%0) >>> 0 == 0 */ + public static final Call REM_ZERO_LONG = staticCall(JSTYPE_LOOKUP, JSType.class, "remZero", long.class, long.class, long.class); + /** Mod exact wrapper for potentially integer remainders that turns into float point */ public static final Call REM_EXACT_LONG = staticCall(JSTYPE_LOOKUP, JSType.class, "remExact", long.class, long.class, long.class, int.class); @@ -700,6 +714,9 @@ public enum JSType { * @return a number */ public static double toNumber(final Object obj) { + if (obj instanceof Double) { + return (Double)obj; + } if (obj instanceof Number) { return ((Number)obj).doubleValue(); } @@ -1002,6 +1019,16 @@ public enum JSType { } /** + * JavaScript compliant int to uint32 conversion + * + * @param num an int + * @return a uint32 + */ + public static long toUint32(final int num) { + return num & MAX_UINT; + } + + /** * JavaScript compliant Object to uint16 conversion * ECMA 9.7 ToUint16: (Unsigned 16 Bit Integer) * @@ -1474,6 +1501,28 @@ public enum JSType { } /** + * Implements int division but allows {@code x / 0} to be represented as 0. Basically equivalent to + * {@code (x / y)|0} JavaScript expression (division of two ints coerced to int). + * @param x the dividend + * @param y the divisor + * @return the result + */ + public static int divZero(final int x, final int y) { + return y == 0 ? 0 : x / y; + } + + /** + * Implements int remainder but allows {@code x % 0} to be represented as 0. Basically equivalent to + * {@code (x % y)|0} JavaScript expression (remainder of two ints coerced to int). + * @param x the dividend + * @param y the divisor + * @return the remainder + */ + public static int remZero(final int x, final int y) { + return y == 0 ? 0 : x % y; + } + + /** * Wrapper for modExact. Throws UnwarrantedOptimismException if the modulo can't be represented as int. * * @param x first term @@ -1517,6 +1566,28 @@ public enum JSType { } /** + * Implements long division but allows {@code x / 0} to be represented as 0. Useful when division of two longs + * is coerced to long. + * @param x the dividend + * @param y the divisor + * @return the result + */ + public static long divZero(final long x, final long y) { + return y == 0L ? 0L : x / y; + } + + /** + * Implements long remainder but allows {@code x % 0} to be represented as 0. Useful when remainder of two longs + * is coerced to long. + * @param x the dividend + * @param y the divisor + * @return the remainder + */ + public static long remZero(final long x, final long y) { + return y == 0L ? 0L : x % y; + } + + /** * Wrapper for modExact. Throws UnwarrantedOptimismException if the modulo can't be represented as int. * * @param x first term @@ -1776,6 +1847,23 @@ public enum JSType { } /** + * Returns the boxed version of a primitive class + * @param clazz the class + * @return the boxed type of clazz, or unchanged if not primitive + */ + public static Class<?> getBoxedClass(final Class<?> clazz) { + if (clazz == int.class) { + return Integer.class; + } else if (clazz == long.class) { + return Long.class; + } else if (clazz == double.class) { + return Double.class; + } + assert !clazz.isPrimitive(); + return clazz; + } + + /** * Create a method handle constant of the correct primitive type * for a constant object * @param o object diff --git a/src/jdk/nashorn/internal/runtime/Property.java b/src/jdk/nashorn/internal/runtime/Property.java index f57246ca..4225c251 100644 --- a/src/jdk/nashorn/internal/runtime/Property.java +++ b/src/jdk/nashorn/internal/runtime/Property.java @@ -84,14 +84,17 @@ public abstract class Property implements Serializable { public static final int IS_NASGEN_PRIMITIVE = 1 << 6; /** Is this a builtin property, e.g. Function.prototype.apply */ - public static final int IS_BUILTIN = 1 << 7; + public static final int IS_BUILTIN = 1 << 7; /** Is this property bound to a receiver? This means get/set operations will be delegated to * a statically defined object instead of the object passed as callsite parameter. */ - public static final int IS_BOUND = 1 << 7; + public static final int IS_BOUND = 1 << 8; /** Is this a lexically scoped LET or CONST variable that is dead until it is declared. */ - public static final int NEEDS_DECLARATION = 1 << 8; + public static final int NEEDS_DECLARATION = 1 << 9; + + /** Is this property an ES6 lexical binding? */ + public static final int IS_LEXICAL_BINDING = 1 << 10; /** Property key. */ private final String key; @@ -102,6 +105,13 @@ public abstract class Property implements Serializable { /** Property field number or spill slot. */ private final int slot; + /** + * Current type of this object, in object only mode, this is an Object.class. In dual-fields mode + * null means undefined, and primitive types are allowed. The reason a special type is used for + * undefined, is that are no bits left to represent it in primitive types + */ + private Class<?> type; + /** SwitchPoint that is invalidated when property is changed, optional */ protected transient SwitchPoint builtinSwitchPoint; @@ -536,7 +546,7 @@ public abstract class Property implements Serializable { * <p> * see {@link ObjectClassGenerator#createSetter(Class, Class, MethodHandle, MethodHandle)} * if you are interested in the internal details of this. Note that if you - * are running in default mode, with {@code -Dnashorn.fields.dual=true}, disabled, the setters + * are running with {@code -Dnashorn.fields.objects=true}, the setters * will currently never change, as all properties are represented as Object field, * the Object fields are Initialized to {@code ScriptRuntime.UNDEFINED} and primitives are * boxed/unboxed upon every access, which is not necessarily optimal @@ -569,7 +579,7 @@ public abstract class Property implements Serializable { @Override public int hashCode() { - final Class<?> type = getCurrentType(); + final Class<?> type = getLocalType(); return Objects.hashCode(this.key) ^ flags ^ getSlot() ^ (type == null ? 0 : type.hashCode()); } @@ -586,7 +596,7 @@ public abstract class Property implements Serializable { final Property otherProperty = (Property)other; return equalsWithoutType(otherProperty) && - getCurrentType() == otherProperty.getCurrentType(); + getLocalType() == otherProperty.getLocalType(); } boolean equalsWithoutType(final Property otherProperty) { @@ -615,7 +625,7 @@ public abstract class Property implements Serializable { */ public final String toStringShort() { final StringBuilder sb = new StringBuilder(); - final Class<?> type = getCurrentType(); + final Class<?> type = getLocalType(); sb.append(getKey()).append(" (").append(type(type)).append(')'); return sb.toString(); } @@ -632,7 +642,7 @@ public abstract class Property implements Serializable { @Override public String toString() { final StringBuilder sb = new StringBuilder(); - final Class<?> type = getCurrentType(); + final Class<?> type = getLocalType(); sb.append(indent(getKey(), 20)). append(" id="). @@ -656,20 +666,40 @@ public abstract class Property implements Serializable { } /** - * Get the current type of this field. If you are not running with dual fields enabled, + * Get the current type of this property. If you are running with object fields enabled, * this will always be Object.class. See the value representation explanation in * {@link Property#getSetter(Class, PropertyMap)} and {@link ObjectClassGenerator} * for more information. * + * <p>Note that for user accessor properties, this returns the type of the last observed + * value passed to or returned by a user accessor. Use {@link #getLocalType()} to always get + * the type of the actual value stored in the property slot.</p> + * * @return current type of property, null means undefined */ - public abstract Class<?> getCurrentType(); + public final Class<?> getType() { + return type; + } + + /** + * Set the type of this property. + * @param type new type + */ + public final void setType(final Class<?> type) { + assert type != boolean.class : "no boolean storage support yet - fix this"; + this.type = type == null ? null : type.isPrimitive() ? type : Object.class; + } /** - * Reset the current type of this property - * @param currentType new current type + * Get the type of the value in the local property slot. This returns the same as + * {@link #getType()} for normal properties, but always returns {@code Object.class} + * for {@link UserAccessorProperty}s as their local type is a pair of accessor references. + * + * @return the local property type */ - public abstract void setCurrentType(final Class<?> currentType); + protected Class<?> getLocalType() { + return getType(); + } /** * Check whether this Property can ever change its type. The default is false, and if @@ -687,4 +717,12 @@ public abstract class Property implements Serializable { public boolean isFunctionDeclaration() { return (flags & IS_FUNCTION_DECLARATION) == IS_FUNCTION_DECLARATION; } + + /** + * Is this a property defined by ES6 let or const? + * @return true if this property represents a lexical binding. + */ + public boolean isLexicalBinding() { + return (flags & IS_LEXICAL_BINDING) == IS_LEXICAL_BINDING; + } } diff --git a/src/jdk/nashorn/internal/runtime/PropertyMap.java b/src/jdk/nashorn/internal/runtime/PropertyMap.java index 61912332..30a15132 100644 --- a/src/jdk/nashorn/internal/runtime/PropertyMap.java +++ b/src/jdk/nashorn/internal/runtime/PropertyMap.java @@ -84,7 +84,7 @@ public final class PropertyMap implements Iterable<Object>, Serializable { private transient WeakHashMap<Property, SoftReference<PropertyMap>> history; /** History of prototypes, used to limit map duplication. */ - private transient WeakHashMap<PropertyMap, SoftReference<PropertyMap>> protoHistory; + private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory; /** property listeners */ private transient PropertyListeners listeners; @@ -512,7 +512,7 @@ public final class PropertyMap implements Iterable<Object>, Serializable { assert sameType || oldProperty instanceof AccessorProperty && newProperty instanceof UserAccessorProperty : - "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getCurrentType() + " => " + newProperty.getCurrentType() + "]"; + "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]"; newMap.flags = flags; @@ -677,14 +677,14 @@ public final class PropertyMap implements Iterable<Object>, Serializable { /** * Check prototype history for an existing property map with specified prototype. * - * @param parentMap New prototype object. + * @param proto New prototype object. * * @return Existing {@link PropertyMap} or {@code null} if not found. */ - private PropertyMap checkProtoHistory(final PropertyMap parentMap) { + private PropertyMap checkProtoHistory(final ScriptObject proto) { final PropertyMap cachedMap; if (protoHistory != null) { - final SoftReference<PropertyMap> weakMap = protoHistory.get(parentMap); + final SoftReference<PropertyMap> weakMap = protoHistory.get(proto); cachedMap = (weakMap != null ? weakMap.get() : null); } else { cachedMap = null; @@ -700,15 +700,15 @@ public final class PropertyMap implements Iterable<Object>, Serializable { /** * Add a map to the prototype history. * - * @param parentMap Prototype to add (key.) + * @param newProto Prototype to add (key.) * @param newMap {@link PropertyMap} associated with prototype. */ - private void addToProtoHistory(final PropertyMap parentMap, final PropertyMap newMap) { + private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { if (protoHistory == null) { protoHistory = new WeakHashMap<>(); } - protoHistory.put(parentMap, new SoftReference<>(newMap)); + protoHistory.put(newProto, new SoftReference<>(newMap)); } /** @@ -883,8 +883,7 @@ public final class PropertyMap implements Iterable<Object>, Serializable { */ public PropertyMap changeProto(final ScriptObject newProto) { - final PropertyMap parentMap = newProto == null ? null : newProto.getMap(); - final PropertyMap nextMap = checkProtoHistory(parentMap); + final PropertyMap nextMap = checkProtoHistory(newProto); if (nextMap != null) { return nextMap; } @@ -894,7 +893,7 @@ public final class PropertyMap implements Iterable<Object>, Serializable { } final PropertyMap newMap = new PropertyMap(this); - addToProtoHistory(parentMap, newMap); + addToProtoHistory(newProto, newMap); return newMap; } diff --git a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java index 6b2c7620..06414edb 100644 --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java @@ -26,7 +26,6 @@ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.lookup.Lookup.MH; - import java.io.IOException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -475,6 +474,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp * @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. */ + @SuppressWarnings("unused") private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints( final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) { if(invalidatedProgramPoints != null) { @@ -619,20 +619,25 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp return f; } - MethodHandle lookup(final FunctionInitializer fnInit) { + private void logLookup(final boolean shouldLog, final MethodType targetType) { + if (shouldLog && log.isEnabled()) { + log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType); + } + } + + private MethodHandle lookup(final FunctionInitializer fnInit, final boolean shouldLog) { final MethodType type = fnInit.getMethodType(); + logLookup(shouldLog, type); return lookupCodeMethod(fnInit.getCode(), type); } MethodHandle lookup(final FunctionNode fn) { final MethodType type = new FunctionSignature(fn).getMethodType(); + logLookup(true, type); return lookupCodeMethod(fn.getCompileUnit().getCode(), type); } MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) { - if (log.isEnabled()) { - log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType); - } return MH.findStatic(LOOKUP, codeClass, functionName, targetType); } @@ -648,7 +653,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp if(!code.isEmpty()) { throw new IllegalStateException(name); } - addCode(lookup(initializer), null, null, initializer.getFlags()); + addCode(lookup(initializer, true), null, null, initializer.getFlags()); } private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints, @@ -670,10 +675,10 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp */ private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) { if (isVariableArity()) { - return addCode(lookup(fnInit), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); + return addCode(lookup(fnInit, true), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); } - final MethodHandle handle = lookup(fnInit); + final MethodHandle handle = lookup(fnInit, true); final MethodType fromType = handle.type(); MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1); toType = toType.changeReturnType(fromType.returnType()); @@ -698,7 +703,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp toType = toType.dropParameterTypes(fromCount, toCount); } - return addCode(lookup(fnInit).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); + return addCode(lookup(fnInit, false).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); } /** @@ -727,7 +732,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp assert existingBest != null; //we are calling a vararg method with real args - boolean applyToCall = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType); + boolean varArgWithRealArgs = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType); //if the best one is an apply to call, it has to match the callsite exactly //or we need to regenerate @@ -736,14 +741,16 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp if (best != null) { return best; } - applyToCall = true; + varArgWithRealArgs = true; } - if (applyToCall) { + if (varArgWithRealArgs) { + // special case: we had an apply to call, but we failed to make it fit. + // Try to generate a specialized one for this callsite. It may + // be another apply to call specialization, or it may not, but whatever + // it is, it is a specialization that is guaranteed to fit final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false); - if ((fnInit.getFlags() & FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION) != 0) { //did the specialization work - existingBest = addCode(fnInit, callSiteType); - } + existingBest = addCode(fnInit, callSiteType); } return existingBest; diff --git a/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java b/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java index cd528a01..c7fd0190 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java +++ b/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java @@ -212,6 +212,7 @@ public final class ScriptEnvironment { * @param out output print writer * @param err error print writer */ + @SuppressWarnings("unused") public ScriptEnvironment(final Options options, final PrintWriter out, final PrintWriter err) { this.out = out; this.err = err; diff --git a/src/jdk/nashorn/internal/runtime/ScriptFunction.java b/src/jdk/nashorn/internal/runtime/ScriptFunction.java index 0ba06b3a..d999c118 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptFunction.java +++ b/src/jdk/nashorn/internal/runtime/ScriptFunction.java @@ -603,16 +603,6 @@ public abstract class ScriptFunction extends ScriptObject { log.info("Linking optimistic builtin function: '", name, "' args=", Arrays.toString(request.getArguments()), " desc=", desc); } - final SwitchPoint[] msps = linkLogic.getModificationSwitchPoints(); - if (msps != null) { - for (final SwitchPoint sp : msps) { - if (sp != null) { - assert !sp.hasBeenInvalidated(); - sps.add(sp); - } - } - } - exceptionGuard = linkLogic.getRelinkException(); break; diff --git a/src/jdk/nashorn/internal/runtime/ScriptObject.java b/src/jdk/nashorn/internal/runtime/ScriptObject.java index 87d28976..c3eca18f 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptObject.java +++ b/src/jdk/nashorn/internal/runtime/ScriptObject.java @@ -46,7 +46,10 @@ import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_ import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.isScopeFlag; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.isStrictFlag; import static jdk.nashorn.internal.runtime.linker.NashornGuards.explicitInstanceOfCheck; + import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -97,7 +100,7 @@ import jdk.nashorn.internal.runtime.linker.NashornGuards; * </ul> */ -public abstract class ScriptObject implements PropertyAccess { +public abstract class ScriptObject implements PropertyAccess, Cloneable { /** __proto__ special property name inside object literals. ES6 draft. */ public static final String PROTO_PROPERTY_NAME = "__proto__"; @@ -303,29 +306,44 @@ public abstract class ScriptObject implements PropertyAccess { PropertyMap newMap = this.getMap(); for (final Property property : properties) { - final String key = property.getKey(); - final Property oldProp = newMap.findProperty(key); - if (oldProp == null) { - if (property instanceof UserAccessorProperty) { - // Note: we copy accessor functions to this object which is semantically different from binding. - final UserAccessorProperty prop = this.newUserAccessors(key, property.getFlags(), property.getGetterFunction(source), property.getSetterFunction(source)); - newMap = newMap.addPropertyNoHistory(prop); - } else { - newMap = newMap.addPropertyBind((AccessorProperty)property, source); - } + newMap = addBoundProperty(newMap, source, property); + } + + this.setMap(newMap); + } + + /** + * Add a bound property from {@code source}, using the interim property map {@code propMap}, and return the + * new interim property map. + * + * @param propMap the property map + * @param source the source object + * @param property the property to be added + * @return the new property map + */ + protected PropertyMap addBoundProperty(final PropertyMap propMap, final ScriptObject source, final Property property) { + PropertyMap newMap = propMap; + final String key = property.getKey(); + final Property oldProp = newMap.findProperty(key); + if (oldProp == null) { + if (property instanceof UserAccessorProperty) { + // Note: we copy accessor functions to this object which is semantically different from binding. + final UserAccessorProperty prop = this.newUserAccessors(key, property.getFlags(), property.getGetterFunction(source), property.getSetterFunction(source)); + newMap = newMap.addPropertyNoHistory(prop); } else { - // See ECMA section 10.5 Declaration Binding Instantiation - // step 5 processing each function declaration. - if (property.isFunctionDeclaration() && !oldProp.isConfigurable()) { - if (oldProp instanceof UserAccessorProperty || - !(oldProp.isWritable() && oldProp.isEnumerable())) { - throw typeError("cant.redefine.property", key, ScriptRuntime.safeToString(this)); - } + newMap = newMap.addPropertyBind((AccessorProperty)property, source); + } + } else { + // See ECMA section 10.5 Declaration Binding Instantiation + // step 5 processing each function declaration. + if (property.isFunctionDeclaration() && !oldProp.isConfigurable()) { + if (oldProp instanceof UserAccessorProperty || + !(oldProp.isWritable() && oldProp.isEnumerable())) { + throw typeError("cant.redefine.property", key, ScriptRuntime.safeToString(this)); } } } - - this.setMap(newMap); + return newMap; } /** @@ -510,6 +528,17 @@ public abstract class ScriptObject implements PropertyAccess { } /** + * Invalidate any existing global constant method handles that may exist for {@code key}. + * @param key the property name + */ + protected void invalidateGlobalConstant(final String key) { + final GlobalConstants globalConstants = getGlobalConstants(); + if (globalConstants != null) { + globalConstants.delete(key); + } + } + + /** * ECMA 8.12.9 [[DefineOwnProperty]] (P, Desc, Throw) * * @param key the property key @@ -524,6 +553,8 @@ public abstract class ScriptObject implements PropertyAccess { final Object current = getOwnPropertyDescriptor(key); final String name = JSType.toString(key); + invalidateGlobalConstant(key); + if (current == UNDEFINED) { if (isExtensible()) { // add a new own property @@ -692,8 +723,7 @@ public abstract class ScriptObject implements PropertyAccess { 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)); + setArray(getArray().ensure(longIndex).set(index,value, false)); } private void checkIntegerKey(final String key) { @@ -923,7 +953,8 @@ public abstract class ScriptObject implements PropertyAccess { if (property instanceof UserAccessorProperty) { ((UserAccessorProperty)property).setAccessors(this, getMap(), null); } - Global.getConstants().delete(property.getKey()); + + invalidateGlobalConstant(property.getKey()); return true; } } @@ -970,7 +1001,7 @@ public abstract class ScriptObject implements PropertyAccess { final UserAccessorProperty uc = (UserAccessorProperty)oldProperty; final int slot = uc.getSlot(); - assert uc.getCurrentType() == Object.class; + assert uc.getLocalType() == Object.class; if (slot >= spillLength) { uc.setAccessors(this, getMap(), new UserAccessorProperty.Accessors(getter, setter)); } else { @@ -1349,12 +1380,9 @@ public abstract class ScriptObject implements PropertyAccess { final PropertyMap selfMap = this.getMap(); final ArrayData array = getArray(); - final long length = array.length(); - for (long i = 0; i < length; i = array.nextIndex(i)) { - if (array.has((int)i)) { - keys.add(JSType.toString(i)); - } + for (final Iterator<Long> iter = array.indexIterator(); iter.hasNext(); ) { + keys.add(JSType.toString(iter.next().longValue())); } for (final Property property : selfMap.getProperties()) { @@ -1462,9 +1490,8 @@ public abstract class ScriptObject implements PropertyAccess { //invalidate any fast array setters final ArrayData array = getArray(); - if (array != null) { - array.invalidateSetters(); - } + assert array != null; + setArray(ArrayData.preventExtension(array)); return this; } @@ -1514,12 +1541,12 @@ public abstract class ScriptObject implements PropertyAccess { * * @return {@code true} if 'length' property is non-writable */ - public final boolean isLengthNotWritable() { + public boolean isLengthNotWritable() { return (flags & IS_LENGTH_NOT_WRITABLE) != 0; } /** - * Flag this object as having non-writable length property + * Flag this object as having non-writable length property. */ public void setIsLengthNotWritable() { flags |= IS_LENGTH_NOT_WRITABLE; @@ -1974,20 +2001,22 @@ public abstract class ScriptObject implements PropertyAccess { if (find == null) { switch (operator) { + case "getElem": // getElem only gets here if element name is constant, so treat it like a property access case "getProp": return noSuchProperty(desc, request); case "getMethod": return noSuchMethod(desc, request); - case "getElem": - return createEmptyGetter(desc, explicitInstanceOfCheck, name); default: throw new AssertionError(operator); // never invoked with any other operation } } - final GuardedInvocation cinv = Global.getConstants().findGetMethod(find, this, desc); - if (cinv != null) { - return cinv; + final GlobalConstants globalConstants = getGlobalConstants(); + if (globalConstants != null) { + final GuardedInvocation cinv = globalConstants.findGetMethod(find, this, desc); + if (cinv != null) { + return cinv; + } } final Class<?> returnType = desc.getMethodType().returnType(); @@ -2174,6 +2203,9 @@ public abstract class ScriptObject implements PropertyAccess { if (find != null) { if (!find.getProperty().isWritable() && !NashornCallSiteDescriptor.isDeclaration(desc)) { + if (NashornCallSiteDescriptor.isScope(desc) && find.getProperty().isLexicalBinding()) { + throw typeError("assign.constant", name); // Overwriting ES6 const should throw also in non-strict mode. + } // Existing, non-writable property return createEmptySetMethod(desc, explicitInstanceOfCheck, "property.not.writable", true); } @@ -2185,14 +2217,22 @@ public abstract class ScriptObject implements PropertyAccess { final GuardedInvocation inv = new SetMethodCreator(this, find, desc, request).createGuardedInvocation(findBuiltinSwitchPoint(name)); - final GuardedInvocation cinv = Global.getConstants().findSetMethod(find, this, inv, desc, request); - if (cinv != null) { - return cinv; + final GlobalConstants globalConstants = getGlobalConstants(); + if (globalConstants != null) { + final GuardedInvocation cinv = globalConstants.findSetMethod(find, this, inv, desc, request); + if (cinv != null) { + return cinv; + } } return inv; } + private GlobalConstants getGlobalConstants() { + // Avoid hitting getContext() which might be costly for a non-Global unless needed. + return GlobalConstants.GLOBAL_ONLY && !isGlobal() ? null : getContext().getGlobalConstants(); + } + private GuardedInvocation createEmptySetMethod(final CallSiteDescriptor desc, final boolean explicitInstanceOfCheck, final String strictErrorMessage, final boolean canBeFastScope) { final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); if (NashornCallSiteDescriptor.isStrict(desc)) { @@ -2292,8 +2332,9 @@ public abstract class ScriptObject implements PropertyAccess { } final ScriptFunction func = (ScriptFunction)value; - final Object thiz = scopeCall && func.isStrict() ? ScriptRuntime.UNDEFINED : this; + final Object thiz = scopeCall && func.isStrict() ? UNDEFINED : this; // TODO: It'd be awesome if we could bind "name" without binding "this". + // Since we're binding this we must use an identity guard here. return new GuardedInvocation( MH.dropArguments( MH.constant( @@ -2301,9 +2342,9 @@ public abstract class ScriptObject implements PropertyAccess { func.makeBoundFunction(thiz, new Object[] { name })), 0, Object.class), - NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck), - (SwitchPoint)null, - explicitInstanceOfCheck ? null : ClassCastException.class); + NashornGuards.combineGuards( + NashornGuards.getIdentityGuard(this), + NashornGuards.getMapGuard(getMap(), true))); } /** @@ -2645,20 +2686,22 @@ public abstract class ScriptObject implements PropertyAccess { * @param newLength new length to set */ public final void setLength(final long newLength) { - final long arrayLength = getArray().length(); - if (newLength == arrayLength) { - return; - } + ArrayData data = getArray(); + final long arrayLength = data.length(); + if (newLength == arrayLength) { + return; + } - if (newLength > arrayLength) { - setArray(getArray().ensure(newLength - 1)); - if (getArray().canDelete(arrayLength, newLength - 1, false)) { - setArray(getArray().delete(arrayLength, newLength - 1)); - } - return; - } + if (newLength > arrayLength) { + data = data.ensure(newLength - 1); + if (data.canDelete(arrayLength, newLength - 1, false)) { + data = data.delete(arrayLength, newLength - 1); + } + setArray(data); + return; + } - if (newLength < arrayLength) { + if (newLength < arrayLength) { long actualLength = newLength; // Check for numeric keys in property map and delete them or adjust length, depending on whether @@ -2680,8 +2723,8 @@ public abstract class ScriptObject implements PropertyAccess { } } - setArray(getArray().shrink(actualLength)); - getArray().setLength(actualLength); + setArray(data.shrink(actualLength)); + data.setLength(actualLength); } } @@ -3065,7 +3108,7 @@ public abstract class ScriptObject implements PropertyAccess { private boolean doesNotHaveEnsureLength(final long longIndex, final long oldLength, final int callSiteFlags) { if (longIndex >= oldLength) { if (!isExtensible()) { - if (NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)) { + if (isStrictFlag(callSiteFlags)) { throw typeError("object.non.extensible", JSType.toString(longIndex), ScriptRuntime.safeToString(this)); } return true; @@ -3089,7 +3132,7 @@ public abstract class ScriptObject implements PropertyAccess { final long oldLength = getArray().length(); final long longIndex = ArrayIndex.toLongIndex(index); if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) { - final boolean strict = NashornCallSiteDescriptor.isStrictFlag(callSiteFlags); + final boolean strict = isStrictFlag(callSiteFlags); setArray(getArray().set(index, value, strict)); doesNotHaveEnsureDelete(longIndex, oldLength, strict); } @@ -3099,7 +3142,7 @@ public abstract class ScriptObject implements PropertyAccess { final long oldLength = getArray().length(); final long longIndex = ArrayIndex.toLongIndex(index); if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) { - final boolean strict = NashornCallSiteDescriptor.isStrictFlag(callSiteFlags); + final boolean strict = isStrictFlag(callSiteFlags); setArray(getArray().set(index, value, strict)); doesNotHaveEnsureDelete(longIndex, oldLength, strict); } @@ -3109,7 +3152,7 @@ public abstract class ScriptObject implements PropertyAccess { final long oldLength = getArray().length(); final long longIndex = ArrayIndex.toLongIndex(index); if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) { - final boolean strict = NashornCallSiteDescriptor.isStrictFlag(callSiteFlags); + final boolean strict = isStrictFlag(callSiteFlags); setArray(getArray().set(index, value, strict)); doesNotHaveEnsureDelete(longIndex, oldLength, strict); } @@ -3119,7 +3162,7 @@ public abstract class ScriptObject implements PropertyAccess { final long oldLength = getArray().length(); final long longIndex = ArrayIndex.toLongIndex(index); if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) { - final boolean strict = NashornCallSiteDescriptor.isStrictFlag(callSiteFlags); + final boolean strict = isStrictFlag(callSiteFlags); setArray(getArray().set(index, value, strict)); doesNotHaveEnsureDelete(longIndex, oldLength, strict); } @@ -3137,8 +3180,10 @@ public abstract class ScriptObject implements PropertyAccess { public final void setObject(final FindProperty find, final int callSiteFlags, final String key, final Object value) { FindProperty f = find; + invalidateGlobalConstant(key); + if (f != null && f.isInherited() && !(f.getProperty() instanceof UserAccessorProperty)) { - final boolean isScope = NashornCallSiteDescriptor.isScopeFlag(callSiteFlags); + final boolean isScope = isScopeFlag(callSiteFlags); // If the start object of the find is not this object it means the property was found inside a // 'with' statement expression (see WithObject.findProperty()). In this case we forward the 'set' // to the 'with' object. @@ -3159,17 +3204,19 @@ public abstract class ScriptObject implements PropertyAccess { if (f != null) { if (!f.getProperty().isWritable()) { - if (NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)) { + if (isScopeFlag(callSiteFlags) && f.getProperty().isLexicalBinding()) { + throw typeError("assign.constant", key); // Overwriting ES6 const should throw also in non-strict mode. + } + if (isStrictFlag(callSiteFlags)) { throw typeError("property.not.writable", key, ScriptRuntime.safeToString(this)); } - return; } - f.setValue(value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)); + f.setValue(value, isStrictFlag(callSiteFlags)); } else if (!isExtensible()) { - if (NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)) { + if (isStrictFlag(callSiteFlags)) { throw typeError("object.non.extensible", key, ScriptRuntime.safeToString(this)); } } else { @@ -3194,8 +3241,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(primitiveKey); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3213,8 +3261,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(primitiveKey); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3232,8 +3281,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(primitiveKey); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3251,8 +3301,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(primitiveKey); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3269,8 +3320,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3287,8 +3339,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3305,8 +3358,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3323,8 +3377,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3341,8 +3396,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3359,8 +3415,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3377,8 +3434,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3395,8 +3453,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3413,7 +3472,8 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3429,8 +3489,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3447,8 +3508,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3465,8 +3527,9 @@ public abstract class ScriptObject implements PropertyAccess { final int index = getArrayIndex(key); if (isValidArrayIndex(index)) { - if (getArray().has(index)) { - setArray(getArray().set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags))); + final ArrayData data = getArray(); + if (data.has(index)) { + setArray(data.set(index, value, isStrictFlag(callSiteFlags))); } else { doesNotHave(index, value, callSiteFlags); } @@ -3557,7 +3620,6 @@ public abstract class ScriptObject implements PropertyAccess { } return false; } - return deleteObject(JSType.toObject(key), strict); } @@ -3632,6 +3694,31 @@ public abstract class ScriptObject implements PropertyAccess { } /** + * Return a shallow copy of this ScriptObject. + * @return a shallow copy. + */ + public final ScriptObject copy() { + try { + return clone(); + } catch (final CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + @Override + protected ScriptObject clone() throws CloneNotSupportedException { + final ScriptObject clone = (ScriptObject) super.clone(); + if (objectSpill != null) { + clone.objectSpill = objectSpill.clone(); + if (primitiveSpill != null) { + clone.primitiveSpill = primitiveSpill.clone(); + } + } + clone.arrayData = arrayData.copy(); + return clone; + } + + /** * Make a new UserAccessorProperty property. getter and setter functions are stored in * this ScriptObject and slot values are used in property object. * diff --git a/src/jdk/nashorn/internal/runtime/SpillProperty.java b/src/jdk/nashorn/internal/runtime/SpillProperty.java index 8ff1b8e5..7b42b2bf 100644 --- a/src/jdk/nashorn/internal/runtime/SpillProperty.java +++ b/src/jdk/nashorn/internal/runtime/SpillProperty.java @@ -161,12 +161,12 @@ public class SpillProperty extends AccessorProperty { */ public SpillProperty(final String key, final int flags, final int slot) { super(key, flags, slot, primitiveGetter(slot), primitiveSetter(slot), objectGetter(slot), objectSetter(slot)); - assert !OBJECT_FIELDS_ONLY || getCurrentType() == Object.class; + assert !OBJECT_FIELDS_ONLY || getLocalType() == Object.class; } SpillProperty(final String key, final int flags, final int slot, final Class<?> initialType) { this(key, flags, slot); - setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : initialType); + setType(OBJECT_FIELDS_ONLY ? Object.class : initialType); } SpillProperty(final String key, final int flags, final int slot, final ScriptObject owner, final Object initialValue) { diff --git a/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java b/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java index 5fdec009..d14dd8e7 100644 --- a/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java +++ b/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java @@ -27,16 +27,16 @@ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; -import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT_OPTIMISTIC; -import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; +import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.util.concurrent.Callable; import jdk.nashorn.internal.lookup.Lookup; import jdk.nashorn.internal.runtime.linker.Bootstrap; +import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; /** * Property with user defined getters/setters. Actual getter and setter @@ -69,38 +69,29 @@ public final class UserAccessorProperty extends SpillProperty { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); /** Getter method handle */ - private final static MethodHandle INVOKE_GETTER_ACCESSOR = findOwnMH_S("invokeGetterAccessor", Object.class, Accessors.class, Object.class); + private final static MethodHandle INVOKE_OBJECT_GETTER = findOwnMH_S("invokeObjectGetter", Object.class, Accessors.class, MethodHandle.class, Object.class); + private final static MethodHandle INVOKE_INT_GETTER = findOwnMH_S("invokeIntGetter", int.class, Accessors.class, MethodHandle.class, int.class, Object.class); + private final static MethodHandle INVOKE_LONG_GETTER = findOwnMH_S("invokeLongGetter", long.class, Accessors.class, MethodHandle.class, int.class, Object.class); + private final static MethodHandle INVOKE_NUMBER_GETTER = findOwnMH_S("invokeNumberGetter", double.class, Accessors.class, MethodHandle.class, int.class, Object.class); /** Setter method handle */ - private final static MethodHandle INVOKE_SETTER_ACCESSOR = findOwnMH_S("invokeSetterAccessor", void.class, Accessors.class, String.class, Object.class, Object.class); - - /** Dynamic invoker for getter */ - private static final Object GETTER_INVOKER_KEY = new Object(); - - private static MethodHandle getINVOKE_UA_GETTER() { - - return Context.getGlobal().getDynamicInvoker(GETTER_INVOKER_KEY, - new Callable<MethodHandle>() { - @Override - public MethodHandle call() { - return Bootstrap.createDynamicInvoker("dyn:call", Object.class, - Object.class, Object.class); - } - }); + private final static MethodHandle INVOKE_OBJECT_SETTER = findOwnMH_S("invokeObjectSetter", void.class, Accessors.class, MethodHandle.class, String.class, Object.class, Object.class); + private final static MethodHandle INVOKE_INT_SETTER = findOwnMH_S("invokeIntSetter", void.class, Accessors.class, MethodHandle.class, String.class, Object.class, int.class); + private final static MethodHandle INVOKE_LONG_SETTER = findOwnMH_S("invokeLongSetter", void.class, Accessors.class, MethodHandle.class, String.class, Object.class, long.class); + private final static MethodHandle INVOKE_NUMBER_SETTER = findOwnMH_S("invokeNumberSetter", void.class, Accessors.class, MethodHandle.class, String.class, Object.class, double.class); + + + static MethodHandle getINVOKE_UA_GETTER(final Class<?> returnType, final int programPoint) { + if (UnwarrantedOptimismException.isValid(programPoint)) { + final int flags = NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC | programPoint << CALLSITE_PROGRAM_POINT_SHIFT; + return Bootstrap.createDynamicInvoker("dyn:call", flags, returnType, Object.class, Object.class); + } else { + return Bootstrap.createDynamicInvoker("dyn:call", Object.class, Object.class, Object.class); + } } - /** Dynamic invoker for setter */ - private static Object SETTER_INVOKER_KEY = new Object(); - - private static MethodHandle getINVOKE_UA_SETTER() { - return Context.getGlobal().getDynamicInvoker(SETTER_INVOKER_KEY, - new Callable<MethodHandle>() { - @Override - public MethodHandle call() { - return Bootstrap.createDynamicInvoker("dyn:call", void.class, - Object.class, Object.class, Object.class); - } - }); + static MethodHandle getINVOKE_UA_SETTER(final Class<?> valueType) { + return Bootstrap.createDynamicInvoker("dyn:call", void.class, Object.class, Object.class, valueType); } /** @@ -158,7 +149,7 @@ public final class UserAccessorProperty extends SpillProperty { } @Override - public Class<?> getCurrentType() { + protected Class<?> getLocalType() { return Object.class; } @@ -189,7 +180,13 @@ public final class UserAccessorProperty extends SpillProperty { @Override public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { - return invokeGetterAccessor(getAccessors((owner != null) ? owner : self), self); + try { + return invokeObjectGetter(getAccessors((owner != null) ? owner : self), getINVOKE_UA_GETTER(Object.class, INVALID_PROGRAM_POINT), self); + } catch (final Error | RuntimeException t) { + throw t; + } catch (final Throwable t) { + throw new RuntimeException(t); + } } @Override @@ -209,41 +206,33 @@ public final class UserAccessorProperty extends SpillProperty { @Override public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { - invokeSetterAccessor(getAccessors((owner != null) ? owner : self), strict ? getKey() : null, self, value); + try { + invokeObjectSetter(getAccessors((owner != null) ? owner : self), getINVOKE_UA_SETTER(Object.class), strict ? getKey() : null, self, value); + } catch (final Error | RuntimeException t) { + throw t; + } catch (final Throwable t) { + throw new RuntimeException(t); + } } @Override public MethodHandle getGetter(final Class<?> type) { //this returns a getter on the format (Accessors, Object receiver) - return Lookup.filterReturnType(INVOKE_GETTER_ACCESSOR, type); + return Lookup.filterReturnType(INVOKE_OBJECT_GETTER, type); } @Override public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) { - //fortype is always object, but in the optimistic world we have to throw - //unwarranted optimism exception for narrower types. We can improve this - //by checking for boxed types and unboxing them, but it is doubtful that - //this gives us any performance, as UserAccessorProperties are typically not - //primitives. Are there? TODO: investigate later. For now we just throw an - //exception for narrower types than object - - if (type.isPrimitive()) { - final MethodHandle getter = getGetter(Object.class); - final MethodHandle mh = - MH.asType( - MH.filterReturnValue( - getter, - MH.insertArguments( - CONVERT_OBJECT_OPTIMISTIC.get(getAccessorTypeIndex(type)), - 1, - programPoint)), - getter.type().changeReturnType(type)); - - return mh; + if (type == int.class) { + return INVOKE_INT_GETTER; + } else if (type == long.class) { + return INVOKE_LONG_GETTER; + } else if (type == double.class) { + return INVOKE_NUMBER_GETTER; + } else { + assert type == Object.class; + return INVOKE_OBJECT_GETTER; } - - assert type == Object.class; - return getGetter(type); } @Override @@ -259,7 +248,16 @@ public final class UserAccessorProperty extends SpillProperty { @Override public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) { - return INVOKE_SETTER_ACCESSOR; + if (type == int.class) { + return INVOKE_INT_SETTER; + } else if (type == long.class) { + return INVOKE_LONG_SETTER; + } else if (type == double.class) { + return INVOKE_NUMBER_SETTER; + } else { + assert type == Object.class; + return INVOKE_OBJECT_SETTER; + } } @Override @@ -282,31 +280,81 @@ public final class UserAccessorProperty extends SpillProperty { // getter/setter may be inherited. If so, proto is bound during lookup. In either // inherited or self case, slot is also bound during lookup. Actual ScriptFunction // to be called is retrieved everytime and applied. - private static Object invokeGetterAccessor(final Accessors gs, final Object self) { + @SuppressWarnings("unused") + private static Object invokeObjectGetter(final Accessors gs, final MethodHandle invoker, final Object self) throws Throwable { final Object func = gs.getter; if (func instanceof ScriptFunction) { - try { - return getINVOKE_UA_GETTER().invokeExact(func, self); - } catch (final Error | RuntimeException t) { - throw t; - } catch (final Throwable t) { - throw new RuntimeException(t); - } + return invoker.invokeExact(func, self); } return UNDEFINED; } - private static void invokeSetterAccessor(final Accessors gs, final String name, final Object self, final Object value) { + @SuppressWarnings("unused") + private static int invokeIntGetter(final Accessors gs, final MethodHandle invoker, final int programPoint, final Object self) throws Throwable { + final Object func = gs.getter; + if (func instanceof ScriptFunction) { + return (int) invoker.invokeExact(func, self); + } + + throw new UnwarrantedOptimismException(UNDEFINED, programPoint); + } + + @SuppressWarnings("unused") + private static long invokeLongGetter(final Accessors gs, final MethodHandle invoker, final int programPoint, final Object self) throws Throwable { + final Object func = gs.getter; + if (func instanceof ScriptFunction) { + return (long) invoker.invokeExact(func, self); + } + + throw new UnwarrantedOptimismException(UNDEFINED, programPoint); + } + + @SuppressWarnings("unused") + private static double invokeNumberGetter(final Accessors gs, final MethodHandle invoker, final int programPoint, final Object self) throws Throwable { + final Object func = gs.getter; + if (func instanceof ScriptFunction) { + return (double) invoker.invokeExact(func, self); + } + + throw new UnwarrantedOptimismException(UNDEFINED, programPoint); + } + + @SuppressWarnings("unused") + private static void invokeObjectSetter(final Accessors gs, final MethodHandle invoker, final String name, final Object self, final Object value) throws Throwable { + final Object func = gs.setter; + if (func instanceof ScriptFunction) { + invoker.invokeExact(func, self, value); + } else if (name != null) { + throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self)); + } + } + + @SuppressWarnings("unused") + private static void invokeIntSetter(final Accessors gs, final MethodHandle invoker, final String name, final Object self, final int value) throws Throwable { + final Object func = gs.setter; + if (func instanceof ScriptFunction) { + invoker.invokeExact(func, self, value); + } else if (name != null) { + throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self)); + } + } + + @SuppressWarnings("unused") + private static void invokeLongSetter(final Accessors gs, final MethodHandle invoker, final String name, final Object self, final long value) throws Throwable { + final Object func = gs.setter; + if (func instanceof ScriptFunction) { + invoker.invokeExact(func, self, value); + } else if (name != null) { + throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self)); + } + } + + @SuppressWarnings("unused") + private static void invokeNumberSetter(final Accessors gs, final MethodHandle invoker, final String name, final Object self, final double value) throws Throwable { final Object func = gs.setter; if (func instanceof ScriptFunction) { - try { - getINVOKE_UA_SETTER().invokeExact(func, self, value); - } catch (final Error | RuntimeException t) { - throw t; - } catch (final Throwable t) { - throw new RuntimeException(t); - } + invoker.invokeExact(func, self, value); } else if (name != null) { throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self)); } diff --git a/src/jdk/nashorn/internal/runtime/arrays/AnyElements.java b/src/jdk/nashorn/internal/runtime/arrays/AnyElements.java new file mode 100644 index 00000000..d9e01923 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/arrays/AnyElements.java @@ -0,0 +1,38 @@ +/* + * 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.arrays; + +/** + * Marker interface for any ContinuousArray with any elements + * Used for type checks that throw ClassCastExceptions and force relinks + * for fast NativeArray specializations of builtin methods + */ +public interface AnyElements { + /** + * Return a numeric weight of the element type - wider is higher + * @return element type weight + */ + public int getElementWeight(); +}
\ No newline at end of file diff --git a/src/jdk/nashorn/internal/runtime/arrays/ArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/ArrayData.java index 9e606ee6..f0a8c7a2 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/ArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/ArrayData.java @@ -28,7 +28,11 @@ package jdk.nashorn.internal.runtime.arrays; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.reflect.Array; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; @@ -37,6 +41,7 @@ import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.PropertyDescriptor; +import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; /** @@ -49,15 +54,18 @@ public abstract class ArrayData { /** Mask for getting a chunk */ protected static final int CHUNK_MASK = CHUNK_SIZE - 1; - /** - * Immutable empty array to get ScriptObjects started. - */ - public static final ArrayData EMPTY_ARRAY = new NoTypeArrayData(); + /** Untouched data - still link callsites as IntArrayData, but expands to + * a proper ArrayData when we try to write to it */ + public static final ArrayData EMPTY_ARRAY = new UntouchedArrayData(); /** * Length of the array data. Not necessarily length of the wrapped array. + * This is private to ensure that no one in a subclass is able to touch the length + * without going through {@link #setLength}. This is used to implement + * {@link LengthNotWritableFilter}s, ensuring that there are no ways past + * a {@link #setLength} function replaced by a nop */ - protected long length; + private long length; /** * Method handle to throw an {@link UnwarrantedOptimismException} when getting an element @@ -66,6 +74,178 @@ public abstract class ArrayData { protected static final CompilerConstants.Call THROW_UNWARRANTED = staticCall(MethodHandles.lookup(), ArrayData.class, "throwUnwarranted", void.class, ArrayData.class, int.class, int.class); /** + * Immutable empty array to get ScriptObjects started. + * Use the same array and convert it to mutable as soon as it is modified + */ + private static class UntouchedArrayData extends ContinuousArrayData { + private UntouchedArrayData() { + super(0); + } + + private ArrayData toRealArrayData() { + return toRealArrayData(0); + } + + private ArrayData toRealArrayData(final int index) { + final IntArrayData newData = new IntArrayData(index + 1); + if (index == 0) { + return newData; + } + return new DeletedRangeArrayFilter(newData, 0, index); + } + + @Override + public ContinuousArrayData copy() { + assert length() == 0; + return this; + } + + @Override + public Object asArrayOfType(final Class<?> componentType) { + return Array.newInstance(componentType, 0); + } + + @Override + public Object[] asObjectArray() { + return ScriptRuntime.EMPTY_ARRAY; + } + + @Override + public ArrayData ensure(final long safeIndex) { + if (safeIndex > 0L) { + if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH) { + return new SparseArrayData(this, safeIndex + 1); + } + //known to fit in int + return toRealArrayData((int)safeIndex).ensure(safeIndex); + } + return this; + } + + @Override + public ArrayData convert(final Class<?> type) { + return toRealArrayData(0).convert(type); + } + + @Override + public ArrayData delete(final int index) { + return new DeletedRangeArrayFilter(this, index, index); + } + + @Override + public ArrayData delete(final long fromIndex, final long toIndex) { + return new DeletedRangeArrayFilter(this, fromIndex, toIndex); + } + + @Override + public void shiftLeft(final int by) { + //nop, always empty or we wouldn't be of this class + } + + @Override + public ArrayData shiftRight(final int by) { + return this; //always empty or we wouldn't be of this class + } + + @Override + public ArrayData shrink(final long newLength) { + return this; + } + + @Override + public ArrayData set(final int index, final Object value, final boolean strict) { + return toRealArrayData(index).set(index, value, strict); + } + + @Override + public ArrayData set(final int index, final int value, final boolean strict) { + return toRealArrayData(index).set(index, value, strict); + } + + @Override + public ArrayData set(final int index, final long value, final boolean strict) { + return toRealArrayData(index).set(index, value, strict); + } + + @Override + public ArrayData set(final int index, final double value, final boolean strict) { + return toRealArrayData(index).set(index, value, strict); + } + + @Override + public int getInt(final int index) { + throw new ArrayIndexOutOfBoundsException(index); //empty + } + + @Override + public long getLong(final int index) { + throw new ArrayIndexOutOfBoundsException(index); //empty + } + + @Override + public double getDouble(final int index) { + throw new ArrayIndexOutOfBoundsException(index); //empty + } + + @Override + public Object getObject(final int index) { + throw new ArrayIndexOutOfBoundsException(index); //empty + } + + @Override + public boolean has(final int index) { + return false; //empty + } + + @Override + public Object pop() { + return ScriptRuntime.UNDEFINED; + } + + @Override + public ArrayData push(final boolean strict, final Object item) { + return toRealArrayData().push(strict, item); + } + + @Override + public ArrayData slice(final long from, final long to) { + return this; //empty + } + + @Override + public ContinuousArrayData fastConcat(final ContinuousArrayData otherData) { + return otherData.copy(); + } + + //no need to override fastPopInt, as the default behavior is to throw classcast exception so we + //can relink and return an undefined, this is the IntArrayData default behavior + @Override + public String toString() { + return getClass().getSimpleName(); + } + + @Override + public MethodHandle getElementGetter(final Class<?> returnType, final int programPoint) { + return null; + } + + @Override + public MethodHandle getElementSetter(final Class<?> elementType) { + return null; + } + + @Override + public Class<?> getElementType() { + return int.class; + } + + @Override + public Class<?> getBoxedElementType() { + return Integer.class; + } + } + + /** * Constructor * @param length Virtual length of the array. */ @@ -77,7 +257,7 @@ public abstract class ArrayData { * Factory method for unspecified array - start as int * @return ArrayData */ - public static ArrayData initialArray() { + public final static ArrayData initialArray() { return new IntArrayData(); } @@ -92,24 +272,13 @@ public abstract class ArrayData { throw new UnwarrantedOptimismException(data.getObject(index), programPoint); } - private static int alignUp(final int size) { - return size + CHUNK_SIZE - 1 & ~(CHUNK_SIZE - 1); - } - - /** - * Generic invalidation hook for script object to have call sites to this array indexing - * relinked, e.g. when a native array is marked as non extensible - */ - public void invalidateGetters() { - //subclass responsibility - } - /** - * Generic invalidation hook for script object to have call sites to this array indexing - * relinked, e.g. when a native array is marked as non extensible + * Align an array size up to the nearest array chunk size + * @param size size required + * @return size given, always >= size */ - public void invalidateSetters() { - //subclass responsibility + protected final static int alignUp(final int size) { + return size + CHUNK_SIZE - 1 & ~(CHUNK_SIZE - 1); } /** @@ -118,7 +287,7 @@ public abstract class ArrayData { * @param length the initial length * @return ArrayData */ - public static ArrayData allocate(final int length) { + public static final ArrayData allocate(final int length) { if (length == 0) { return new IntArrayData(); } else if (length >= SparseArrayData.MAX_DENSE_LENGTH) { @@ -134,7 +303,7 @@ public abstract class ArrayData { * @param array the array * @return ArrayData wrapping this array */ - public static ArrayData allocate(final Object array) { + public static final ArrayData allocate(final Object array) { final Class<?> clazz = array.getClass(); if (clazz == int[].class) { @@ -154,7 +323,7 @@ public abstract class ArrayData { * @param array the array to use for initial elements * @return the ArrayData */ - public static ArrayData allocate(final int[] array) { + public static final ArrayData allocate(final int[] array) { return new IntArrayData(array, array.length); } @@ -164,7 +333,7 @@ public abstract class ArrayData { * @param array the array to use for initial elements * @return the ArrayData */ - public static ArrayData allocate(final long[] array) { + public static final ArrayData allocate(final long[] array) { return new LongArrayData(array, array.length); } @@ -174,7 +343,7 @@ public abstract class ArrayData { * @param array the array to use for initial elements * @return the ArrayData */ - public static ArrayData allocate(final double[] array) { + public static final ArrayData allocate(final double[] array) { return new NumberArrayData(array, array.length); } @@ -184,7 +353,7 @@ public abstract class ArrayData { * @param array the array to use for initial elements * @return the ArrayData */ - public static ArrayData allocate(final Object[] array) { + public static final ArrayData allocate(final Object[] array) { return new ObjectArrayData(array, array.length); } @@ -194,7 +363,7 @@ public abstract class ArrayData { * @param buf the nio ByteBuffer to wrap * @return the ArrayData */ - public static ArrayData allocate(final ByteBuffer buf) { + public static final ArrayData allocate(final ByteBuffer buf) { return new ByteBufferArrayData(buf); } @@ -204,7 +373,7 @@ public abstract class ArrayData { * @param underlying the underlying ArrayData to wrap in the freeze filter * @return the frozen ArrayData */ - public static ArrayData freeze(final ArrayData underlying) { + public static final ArrayData freeze(final ArrayData underlying) { return new FrozenArrayFilter(underlying); } @@ -214,11 +383,31 @@ public abstract class ArrayData { * @param underlying the underlying ArrayData to wrap in the seal filter * @return the sealed ArrayData */ - public static ArrayData seal(final ArrayData underlying) { + public static final ArrayData seal(final ArrayData underlying) { return new SealedArrayFilter(underlying); } /** + * Prevent this array from being extended + * + * @param underlying the underlying ArrayData to wrap in the non extensible filter + * @return new array data, filtered + */ + public static final ArrayData preventExtension(final ArrayData underlying) { + return new NonExtensibleArrayFilter(underlying); + } + + /** + * Prevent this array from having its length reset + * + * @param underlying the underlying ArrayDAta to wrap in the non extensible filter + * @return new array data, filtered + */ + public static final ArrayData setIsLengthNotWritable(final ArrayData underlying) { + return new LengthNotWritableFilter(underlying); + } + + /** * Return the length of the array data. This may differ from the actual * length of the array this wraps as length may be set or gotten as any * other JavaScript Property @@ -271,6 +460,22 @@ public abstract class ArrayData { } /** + * Increase length by 1 + * @return the new length, not the old one (i.e. pre-increment) + */ + protected final long increaseLength() { + return ++this.length; + } + + /** + * Decrease length by 1. + * @return the new length, not the old one (i.e. pre-decrement) + */ + protected final long decreaseLength() { + return --this.length; + } + + /** * Shift the array data left * * TODO: explore start at an index and not at zero, to make these operations @@ -279,7 +484,7 @@ public abstract class ArrayData { * * @param by offset to shift */ - public abstract void shiftLeft(int by); + public abstract void shiftLeft(final int by); /** * Shift the array right @@ -288,7 +493,7 @@ public abstract class ArrayData { * @return New arraydata (or same) */ - public abstract ArrayData shiftRight(int by); + public abstract ArrayData shiftRight(final int by); /** * Ensure that the given index exists and won't fail subsequent @@ -296,7 +501,7 @@ public abstract class ArrayData { * @param safeIndex the index to ensure wont go out of bounds * @return new array data (or same) */ - public abstract ArrayData ensure(long safeIndex); + public abstract ArrayData ensure(final long safeIndex); /** * Shrink the array to a new length, may or may not retain the @@ -306,7 +511,7 @@ public abstract class ArrayData { * * @return new array data (or same) */ - public abstract ArrayData shrink(long newLength); + public abstract ArrayData shrink(final long newLength); /** * Set an object value at a given index @@ -316,7 +521,7 @@ public abstract class ArrayData { * @param strict are we in strict mode * @return new array data (or same) */ - public abstract ArrayData set(int index, Object value, boolean strict); + public abstract ArrayData set(final int index, final Object value, final boolean strict); /** * Set an int value at a given index @@ -326,7 +531,7 @@ public abstract class ArrayData { * @param strict are we in strict mode * @return new array data (or same) */ - public abstract ArrayData set(int index, int value, boolean strict); + public abstract ArrayData set(final int index, final int value, final boolean strict); /** * Set a long value at a given index @@ -336,7 +541,7 @@ public abstract class ArrayData { * @param strict are we in strict mode * @return new array data (or same) */ - public abstract ArrayData set(int index, long value, boolean strict); + public abstract ArrayData set(final int index, final long value, final boolean strict); /** * Set an double value at a given index @@ -346,7 +551,7 @@ public abstract class ArrayData { * @param strict are we in strict mode * @return new array data (or same) */ - public abstract ArrayData set(int index, double value, boolean strict); + public abstract ArrayData set(final int index, final double value, final boolean strict); /** * Set an empty value at a given index. Should only affect Object array. @@ -377,7 +582,7 @@ public abstract class ArrayData { * @param index the index * @return the value */ - public abstract int getInt(int index); + public abstract int getInt(final int index); /** * Returns the optimistic type of this array data. Basically, when an array data object needs to throw an @@ -406,7 +611,7 @@ public abstract class ArrayData { * @param index the index * @return the value */ - public abstract long getLong(int index); + public abstract long getLong(final int index); /** * Get optimistic long - default is that it's impossible. Overridden @@ -426,7 +631,7 @@ public abstract class ArrayData { * @param index the index * @return the value */ - public abstract double getDouble(int index); + public abstract double getDouble(final int index); /** * Get optimistic double - default is that it's impossible. Overridden @@ -446,14 +651,14 @@ public abstract class ArrayData { * @param index the index * @return the value */ - public abstract Object getObject(int index); + public abstract Object getObject(final int index); /** * Tests to see if an entry exists (avoids boxing.) * @param index the index * @return true if entry exists */ - public abstract boolean has(int index); + public abstract boolean has(final int index); /** * Returns if element at specific index can be deleted or not. @@ -499,7 +704,7 @@ public abstract class ArrayData { * @param index the index * @return new array data (or same) */ - public abstract ArrayData delete(int index); + public abstract ArrayData delete(final int index); /** * Delete a given range from this array; @@ -509,7 +714,7 @@ public abstract class ArrayData { * * @return new ArrayData after deletion */ - public abstract ArrayData delete(long fromIndex, long toIndex); + public abstract ArrayData delete(final long fromIndex, final long toIndex); /** * Convert the ArrayData to one with a different element type @@ -519,7 +724,7 @@ public abstract class ArrayData { * @param type new element type * @return new array data */ - public abstract ArrayData convert(Class<?> type); + public abstract ArrayData convert(final Class<?> type); /** * Push an array of items to the end of the array @@ -603,7 +808,7 @@ public abstract class ArrayData { * @param to end index + 1 * @return new array data */ - public abstract ArrayData slice(long from, long to); + public abstract ArrayData slice(final long from, final long to); /** * Fast splice operation. This just modifies the array according to the number of @@ -648,6 +853,34 @@ public abstract class ArrayData { } /** + * Return a list of keys in the array for the iterators + * @return iterator key list + */ + protected List<Long> computeIteratorKeys() { + final List<Long> keys = new ArrayList<>(); + + final long len = length(); + for (long i = 0L; i < len; i = nextIndex(i)) { + if (has((int)i)) { + keys.add(i); + } + } + + return keys; + } + + /** + * Return an iterator that goes through all indexes of elements + * in this array. This includes those after array.length if + * they exist + * + * @return iterator + */ + public Iterator<Long> indexIterator() { + return computeIteratorKeys().iterator(); + } + + /** * Exponential growth function for array size when in * need of resizing. * @@ -666,7 +899,7 @@ public abstract class ArrayData { * * @return the next index */ - public long nextIndex(final long index) { + long nextIndex(final long index) { return index + 1; } @@ -728,5 +961,4 @@ public abstract class ArrayData { public GuardedInvocation findFastSetIndexMethod(final Class<? extends ArrayData> clazz, final CallSiteDescriptor desc, final LinkRequest request) { // array, index, value return null; } - } diff --git a/src/jdk/nashorn/internal/runtime/arrays/ArrayFilter.java b/src/jdk/nashorn/internal/runtime/arrays/ArrayFilter.java index 8d71cc04..9c24a9bf 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/ArrayFilter.java +++ b/src/jdk/nashorn/internal/runtime/arrays/ArrayFilter.java @@ -39,7 +39,7 @@ abstract class ArrayFilter extends ArrayData { protected ArrayData underlying; ArrayFilter(final ArrayData underlying) { - super(underlying.length); + super(underlying.length()); this.underlying = underlying; } @@ -70,62 +70,55 @@ abstract class ArrayFilter extends ArrayData { @Override public void shiftLeft(final int by) { underlying.shiftLeft(by); - setLength(underlying.length); + setLength(underlying.length()); } @Override public ArrayData shiftRight(final int by) { underlying = underlying.shiftRight(by); - setLength(underlying.length); - + setLength(underlying.length()); return this; } @Override public ArrayData ensure(final long safeIndex) { underlying = underlying.ensure(safeIndex); - setLength(underlying.length); - + setLength(underlying.length()); return this; } @Override public ArrayData shrink(final long newLength) { underlying = underlying.shrink(newLength); - setLength(underlying.length); - + setLength(underlying.length()); return this; } @Override public ArrayData set(final int index, final Object value, final boolean strict) { underlying = underlying.set(index, value, strict); - setLength(underlying.length); - + setLength(underlying.length()); return this; } @Override public ArrayData set(final int index, final int value, final boolean strict) { underlying = underlying.set(index, value, strict); - setLength(underlying.length); - + setLength(underlying.length()); return this; } @Override public ArrayData set(final int index, final long value, final boolean strict) { underlying = underlying.set(index, value, strict); - setLength(underlying.length); - + setLength(underlying.length()); return this; } @Override public ArrayData set(final int index, final double value, final boolean strict) { underlying = underlying.set(index, value, strict); - setLength(underlying.length); - + setLength(underlying.length()); return this; } @@ -189,29 +182,28 @@ abstract class ArrayFilter extends ArrayData { @Override public ArrayData delete(final int index) { underlying = underlying.delete(index); - setLength(underlying.length); + setLength(underlying.length()); return this; } @Override public ArrayData delete(final long from, final long to) { underlying = underlying.delete(from, to); - setLength(underlying.length); + setLength(underlying.length()); return this; } @Override public ArrayData convert(final Class<?> type) { underlying = underlying.convert(type); - setLength(underlying.length); + setLength(underlying.length()); return this; } @Override public Object pop() { final Object value = underlying.pop(); - setLength(underlying.length); - + setLength(underlying.length()); return value; } diff --git a/src/jdk/nashorn/internal/runtime/arrays/ArrayIndex.java b/src/jdk/nashorn/internal/runtime/arrays/ArrayIndex.java index 5c857e11..f979aaa9 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/ArrayIndex.java +++ b/src/jdk/nashorn/internal/runtime/arrays/ArrayIndex.java @@ -182,15 +182,15 @@ public final class ArrayIndex { } /** - * Convert an index to a long value. This basically amounts to ANDing it - * with {@link JSType#MAX_UINT}, as the maximum array index in JavaScript + * Convert an index to a long value. This basically amounts to converting it into a + * {@link JSType#toUint32(int)} uint32} as the maximum array index in JavaScript * is 0xfffffffe * * @param index index to convert to long form * @return index as uint32 in a long */ public static long toLongIndex(final int index) { - return index & JSType.MAX_UINT; + return JSType.toUint32(index); } /** @@ -201,7 +201,7 @@ public final class ArrayIndex { * @return index as string */ public static String toKey(final int index) { - return Long.toString(index & JSType.MAX_UINT); + return Long.toString(JSType.toUint32(index)); } } diff --git a/src/jdk/nashorn/internal/runtime/arrays/ContinuousArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/ContinuousArrayData.java index 8724d64a..2fdd184e 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/ContinuousArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/ContinuousArrayData.java @@ -1,5 +1,4 @@ /* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +29,6 @@ import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex; import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; - import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -50,9 +48,6 @@ import jdk.nashorn.internal.runtime.logging.Logger; */ @Logger(name="arrays") public abstract class ContinuousArrayData extends ArrayData { - - private SwitchPoint sp; - /** * Constructor * @param length length (elementLength) @@ -61,18 +56,6 @@ public abstract class ContinuousArrayData extends ArrayData { super(length); } - private SwitchPoint ensureSwitchPointExists() { - if (sp == null){ - sp = new SwitchPoint(); - } - return sp; - } - - @Override - public void invalidateSetters() { - SwitchPoint.invalidateAll(new SwitchPoint[] { ensureSwitchPointExists() }); - } - /** * Check if we can put one more element at the end of this continous * array without reallocating, or if we are overwriting an already @@ -82,7 +65,15 @@ public abstract class ContinuousArrayData extends ArrayData { * @return true if we don't need to do any array reallocation to fit an element at index */ public final boolean hasRoomFor(final int index) { - return has(index) || (index == length && ensure(index) == this); + return has(index) || (index == length() && ensure(index) == this); + } + + /** + * Check if an arraydata is empty + * @return true if empty + */ + public boolean isEmpty() { + return length() == 0L; } /** @@ -109,13 +100,16 @@ public abstract class ContinuousArrayData extends ArrayData { * @param index index to check - currently only int indexes * @return index */ - protected int throwHas(final int index) { + protected final int throwHas(final int index) { if (!has(index)) { throw new ClassCastException(); } return index; } + @Override + public abstract ContinuousArrayData copy(); + /** * Returns the type used to store an element in this array * @return element type @@ -128,6 +122,25 @@ public abstract class ContinuousArrayData extends ArrayData { } /** + * Returns the boxed type of the type used to store an element in this array + * @return element type + */ + public abstract Class<?> getBoxedElementType(); + + /** + * Get the widest element type of two arrays. This can be done faster in subclasses, but + * this works for all ContinuousArrayDatas and for where more optimal checks haven't been + * implemented. + * + * @param otherData another ContinuousArrayData + * @return the widest boxed element type + */ + public ContinuousArrayData widest(final ContinuousArrayData otherData) { + final Class<?> elementType = getElementType(); + return Type.widest(elementType, otherData.getElementType()) == elementType ? this : otherData; + } + + /** * Look up a continuous array element getter * @param get getter, sometimes combined with a has check that throws CCE on failure for relink * @param returnType return type @@ -256,12 +269,7 @@ public abstract class ContinuousArrayData extends ArrayData { final Object[] args = request.getArguments(); final int index = (int)args[args.length - 2]; - //sp may be invalidated by e.g. preventExtensions before the first setter is linked - //then it is already created. otherwise, create it here to guard against future - //invalidations - ensureSwitchPointExists(); - - if (!sp.hasBeenInvalidated() && hasRoomFor(index)) { + if (hasRoomFor(index)) { MethodHandle setElement = getElementSetter(elementType); //Z(continuousarraydata, int, int), return true if successful if (setElement != null) { //else we are dealing with a wider type than supported by this callsite @@ -269,7 +277,7 @@ public abstract class ContinuousArrayData extends ArrayData { getArray = MH.asType(getArray, getArray.type().changeReturnType(getClass())); setElement = MH.filterArguments(setElement, 0, getArray); final MethodHandle guard = MH.insertArguments(FAST_ACCESS_GUARD, 0, clazz); - return new GuardedInvocation(setElement, guard, sp, ClassCastException.class); //CCE if not a scriptObject anymore + return new GuardedInvocation(setElement, guard, (SwitchPoint)null, ClassCastException.class); //CCE if not a scriptObject anymore } } } @@ -344,4 +352,13 @@ public abstract class ContinuousArrayData extends ArrayData { public Object fastPopObject() { throw new ClassCastException(String.valueOf(getClass())); //type is wrong, relink } + + /** + * Specialization - fast concat implementation + * @param otherData data to concat + * @return new arraydata + */ + public ContinuousArrayData fastConcat(final ContinuousArrayData otherData) { + throw new ClassCastException(String.valueOf(getClass()) + " != " + String.valueOf(otherData.getClass())); + } } diff --git a/src/jdk/nashorn/internal/runtime/arrays/DeletedArrayFilter.java b/src/jdk/nashorn/internal/runtime/arrays/DeletedArrayFilter.java index 4f54b639..4fa89f7d 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/DeletedArrayFilter.java +++ b/src/jdk/nashorn/internal/runtime/arrays/DeletedArrayFilter.java @@ -38,8 +38,7 @@ final class DeletedArrayFilter extends ArrayFilter { DeletedArrayFilter(final ArrayData underlying) { super(underlying); - - this.deleted = new BitVector(underlying.length); + this.deleted = new BitVector(underlying.length()); } @Override @@ -79,25 +78,24 @@ final class DeletedArrayFilter extends ArrayFilter { @Override public void shiftLeft(final int by) { super.shiftLeft(by); - deleted.shiftLeft(by, length); + deleted.shiftLeft(by, length()); } @Override public ArrayData shiftRight(final int by) { super.shiftRight(by); - deleted.shiftRight(by, length); - + deleted.shiftRight(by, length()); return this; } @Override public ArrayData ensure(final long safeIndex) { - if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH && safeIndex >= length) { + if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH && safeIndex >= length()) { return new SparseArrayData(this, safeIndex + 1); } super.ensure(safeIndex); - deleted.resize(length); + deleted.resize(length()); return this; } @@ -105,36 +103,31 @@ final class DeletedArrayFilter extends ArrayFilter { @Override public ArrayData shrink(final long newLength) { super.shrink(newLength); - deleted.resize(length); - + deleted.resize(length()); return this; } @Override public ArrayData set(final int index, final Object value, final boolean strict) { deleted.clear(ArrayIndex.toLongIndex(index)); - return super.set(index, value, strict); } @Override public ArrayData set(final int index, final int value, final boolean strict) { deleted.clear(ArrayIndex.toLongIndex(index)); - return super.set(index, value, strict); } @Override public ArrayData set(final int index, final long value, final boolean strict) { deleted.clear(ArrayIndex.toLongIndex(index)); - return super.set(index, value, strict); } @Override public ArrayData set(final int index, final double value, final boolean strict) { deleted.clear(ArrayIndex.toLongIndex(index)); - return super.set(index, value, strict); } @@ -146,7 +139,7 @@ final class DeletedArrayFilter extends ArrayFilter { @Override public ArrayData delete(final int index) { final long longIndex = ArrayIndex.toLongIndex(index); - assert longIndex >= 0 && longIndex < length; + assert longIndex >= 0 && longIndex < length(); deleted.set(longIndex); underlying.setEmpty(index); return this; @@ -154,7 +147,7 @@ final class DeletedArrayFilter extends ArrayFilter { @Override public ArrayData delete(final long fromIndex, final long toIndex) { - assert fromIndex >= 0 && fromIndex <= toIndex && toIndex < length; + assert fromIndex >= 0 && fromIndex <= toIndex && toIndex < length(); deleted.setRange(fromIndex, toIndex + 1); underlying.setEmpty(fromIndex, toIndex); return this; @@ -162,7 +155,7 @@ final class DeletedArrayFilter extends ArrayFilter { @Override public Object pop() { - final long index = length - 1; + final long index = length() - 1; if (super.has((int)index)) { final boolean isDeleted = deleted.isSet(index); @@ -179,7 +172,7 @@ final class DeletedArrayFilter extends ArrayFilter { final ArrayData newArray = underlying.slice(from, to); final DeletedArrayFilter newFilter = new DeletedArrayFilter(newArray); newFilter.getDeleted().copy(deleted); - newFilter.getDeleted().shiftLeft(from, newFilter.length); + newFilter.getDeleted().shiftLeft(from, newFilter.length()); return newFilter; } diff --git a/src/jdk/nashorn/internal/runtime/arrays/DeletedRangeArrayFilter.java b/src/jdk/nashorn/internal/runtime/arrays/DeletedRangeArrayFilter.java index cd5cadb9..953b9213 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/DeletedRangeArrayFilter.java +++ b/src/jdk/nashorn/internal/runtime/arrays/DeletedRangeArrayFilter.java @@ -42,10 +42,10 @@ final class DeletedRangeArrayFilter extends ArrayFilter { } private static ArrayData maybeSparse(final ArrayData underlying, final long hi) { - if(hi < SparseArrayData.MAX_DENSE_LENGTH || underlying instanceof SparseArrayData) { + if (hi < SparseArrayData.MAX_DENSE_LENGTH || underlying instanceof SparseArrayData) { return underlying; } - return new SparseArrayData(underlying, underlying.length); + return new SparseArrayData(underlying, underlying.length()); } private boolean isEmpty() { @@ -93,7 +93,7 @@ final class DeletedRangeArrayFilter extends ArrayFilter { @Override public ArrayData ensure(final long safeIndex) { - if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH && safeIndex >= length) { + if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH && safeIndex >= length()) { return new SparseArrayData(this, safeIndex + 1); } @@ -110,7 +110,7 @@ final class DeletedRangeArrayFilter extends ArrayFilter { @Override public ArrayData shiftRight(final int by) { super.shiftRight(by); - final long len = length; + final long len = length(); lo = Math.min(len, lo + by); hi = Math.min(len - 1, hi + by); @@ -238,7 +238,7 @@ final class DeletedRangeArrayFilter extends ArrayFilter { @Override public Object pop() { - final int index = (int)length - 1; + final int index = (int)length() - 1; if (super.has(index)) { final boolean isDeleted = isDeleted(index); final Object value = super.pop(); diff --git a/src/jdk/nashorn/internal/runtime/arrays/FrozenArrayFilter.java b/src/jdk/nashorn/internal/runtime/arrays/FrozenArrayFilter.java index ccf1d882..1e71b44d 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/FrozenArrayFilter.java +++ b/src/jdk/nashorn/internal/runtime/arrays/FrozenArrayFilter.java @@ -26,9 +26,9 @@ package jdk.nashorn.internal.runtime.arrays; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; - import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.PropertyDescriptor; +import jdk.nashorn.internal.runtime.ScriptRuntime; /** * ArrayData after the array has been frozen by Object.freeze call. @@ -79,4 +79,15 @@ final class FrozenArrayFilter extends SealedArrayFilter { } return this; } + + @Override + public ArrayData push(final boolean strict, final Object... items) { + return this; //nop + } + + @Override + public Object pop() { + final int len = (int)underlying.length(); + return len == 0 ? ScriptRuntime.UNDEFINED : underlying.getObject(len - 1); + } } diff --git a/src/jdk/nashorn/internal/runtime/arrays/IntArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/IntArrayData.java index 0792b6b6..381390ce 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/IntArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/IntArrayData.java @@ -26,7 +26,6 @@ package jdk.nashorn.internal.runtime.arrays; import static jdk.nashorn.internal.codegen.CompilerConstants.specialCall; - import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.Arrays; @@ -57,17 +56,32 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { * @param array an int array * @param length a length, not necessarily array.length */ - IntArrayData(final int array[], final int length) { + IntArrayData(final int[] array, final int length) { super(length); - assert array.length >= length; + assert array == null || array.length >= length; this.array = array; } @Override - public Class<?> getElementType() { + public final Class<?> getElementType() { return int.class; } + @Override + public final Class<?> getBoxedElementType() { + return Integer.class; + } + + @Override + public final int getElementWeight() { + return 1; + } + + @Override + public final ContinuousArrayData widest(final ContinuousArrayData otherData) { + return otherData; + } + private static final MethodHandle HAS_GET_ELEM = specialCall(MethodHandles.lookup(), IntArrayData.class, "getElem", int.class, int.class).methodHandle(); private static final MethodHandle SET_ELEM = specialCall(MethodHandles.lookup(), IntArrayData.class, "setElem", void.class, int.class, int.class).methodHandle(); @@ -104,23 +118,25 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { } @Override - public ArrayData copy() { - return new IntArrayData(array.clone(), (int)length); + public IntArrayData copy() { + return new IntArrayData(array.clone(), (int)length()); } @Override public Object asArrayOfType(final Class<?> componentType) { if (componentType == int.class) { - return array.length == length ? array.clone() : Arrays.copyOf(array, (int)length); + final int len = (int)length(); + return array.length == len ? array.clone() : Arrays.copyOf(array, len); } return super.asArrayOfType(componentType); } private Object[] toObjectArray(final boolean trim) { - assert length <= array.length : "length exceeds internal array size"; - final Object[] oarray = new Object[trim ? (int)length : array.length]; + assert length() <= array.length : "length exceeds internal array size"; + final int len = (int)length(); + final Object[] oarray = new Object[trim ? len : array.length]; - for (int index = 0; index < length; index++) { + for (int index = 0; index < len; index++) { oarray[index] = Integer.valueOf(array[index]); } @@ -128,10 +144,11 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { } private double[] toDoubleArray() { - assert length <= array.length : "length exceeds internal array size"; + assert length() <= array.length : "length exceeds internal array size"; + final int len = (int)length(); final double[] darray = new double[array.length]; - for (int index = 0; index < length; index++) { + for (int index = 0; index < len; index++) { darray[index] = array[index]; } @@ -139,10 +156,11 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { } private long[] toLongArray() { - assert length <= array.length : "length exceeds internal array size"; + assert length() <= array.length : "length exceeds internal array size"; + final int len = (int)length(); final long[] larray = new long[array.length]; - for (int index = 0; index < length; index++) { + for (int index = 0; index < len; index++) { larray[index] = array[index]; } @@ -150,23 +168,22 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { } private LongArrayData convertToLong() { - return new LongArrayData(toLongArray(), (int)length); + return new LongArrayData(toLongArray(), (int)length()); } private NumberArrayData convertToDouble() { - return new NumberArrayData(toDoubleArray(), (int)length); + return new NumberArrayData(toDoubleArray(), (int)length()); } private ObjectArrayData convertToObject() { - return new ObjectArrayData(toObjectArray(false), (int)length); + return new ObjectArrayData(toObjectArray(false), (int)length()); } @Override public ArrayData convert(final Class<?> type) { if (type == Integer.class) { return this; - } - if (type == Long.class) { + } else if (type == Long.class) { return convertToLong(); } else if (type == Double.class) { return convertToDouble(); @@ -183,7 +200,7 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { @Override public ArrayData shiftRight(final int by) { - final ArrayData newData = ensure(by + length - 1); + final ArrayData newData = ensure(by + length() - 1); if (newData != this) { newData.shiftRight(by); return newData; @@ -209,8 +226,7 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { @Override public ArrayData shrink(final long newLength) { - Arrays.fill(array, (int) newLength, array.length, 0); - + Arrays.fill(array, (int)newLength, array.length, 0); return this; } @@ -229,7 +245,7 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { @Override public ArrayData set(final int index, final int value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @@ -238,7 +254,7 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { public ArrayData set(final int index, final long value, final boolean strict) { if (JSType.isRepresentableAsInt(value)) { array[index] = JSType.toInt32(value); - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @@ -249,7 +265,7 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { public ArrayData set(final int index, final double value, final boolean strict) { if (JSType.isRepresentableAsInt(value)) { array[index] = (int)(long)value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @@ -293,7 +309,7 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { @Override public boolean has(final int index) { - return 0 <= index && index < length; + return 0 <= index && index < length(); } @Override @@ -308,11 +324,12 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { @Override public Object pop() { - if (length == 0) { + final int len = (int)length(); + if (len == 0) { return ScriptRuntime.UNDEFINED; } - final int newLength = (int)length - 1; + final int newLength = len - 1; final int elem = array[newLength]; array[newLength] = 0; setLength(newLength); @@ -322,15 +339,12 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { @Override public ArrayData slice(final long from, final long to) { - final long start = from < 0 ? from + length : from; - final long newLength = to - start; - - return new IntArrayData(Arrays.copyOfRange(array, (int)from, (int)to), (int)newLength); + return new IntArrayData(Arrays.copyOfRange(array, (int)from, (int)to), (int)(to - (from < 0 ? from + length() : from))); } @Override public final ArrayData push(final boolean strict, final int item) { - final long len = length; + final long len = length(); final ArrayData newData = ensure(len); if (newData == this) { array[(int)len] = item; @@ -341,13 +355,19 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { @Override public ArrayData fastSplice(final int start, final int removed, final int added) throws UnsupportedOperationException { - final long oldLength = length; + final long oldLength = length(); final long newLength = oldLength - removed + added; if (newLength > SparseArrayData.MAX_DENSE_LENGTH && newLength > array.length) { throw new UnsupportedOperationException(); } final ArrayData returnValue = removed == 0 ? - EMPTY_ARRAY : new IntArrayData(Arrays.copyOfRange(array, start, start + removed), removed); + EMPTY_ARRAY : + new IntArrayData( + Arrays.copyOfRange( + array, + start, + start + removed), + removed); if (newLength != oldLength) { final int[] newArray; @@ -369,21 +389,21 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { @Override public long fastPush(final int arg) { - final int len = (int)length; + final int len = (int)length(); if (len == array.length) { array = Arrays.copyOf(array, nextSize(len)); } array[len] = arg; - return ++length; + return increaseLength(); } //length must not be zero @Override public int fastPopInt() { - if (length == 0) { + if (length() == 0) { throw new ClassCastException(); //relink } - final int newLength = (int)--length; + final int newLength = (int)decreaseLength(); final int elem = array[newLength]; array[newLength] = 0; return elem; @@ -403,4 +423,26 @@ final class IntArrayData extends ContinuousArrayData implements IntElements { public Object fastPopObject() { return fastPopInt(); } + + @Override + public ContinuousArrayData fastConcat(final ContinuousArrayData otherData) { + final int otherLength = (int)otherData.length(); + final int thisLength = (int)length(); + assert otherLength > 0 && thisLength > 0; + + final int[] otherArray = ((IntArrayData)otherData).array; + final int newLength = otherLength + thisLength; + final int[] newArray = new int[ArrayData.alignUp(newLength)]; + + System.arraycopy(array, 0, newArray, 0, thisLength); + System.arraycopy(otherArray, 0, newArray, thisLength, otherLength); + + return new IntArrayData(newArray, newLength); + } + + @Override + public String toString() { + assert length() <= array.length : length() + " > " + array.length; + return getClass().getSimpleName() + ':' + Arrays.toString(Arrays.copyOf(array, (int)length())); + } } diff --git a/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java b/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java index 244739b3..ff4c13e2 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java +++ b/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java @@ -25,11 +25,7 @@ package jdk.nashorn.internal.runtime.arrays; -import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; - -import jdk.nashorn.api.scripting.JSObject; import jdk.nashorn.internal.runtime.Context; -import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.linker.Bootstrap; @@ -98,17 +94,7 @@ public abstract class IteratorAction<T> { * @return result of apply */ public final T apply() { - final boolean strict; - if (callbackfn instanceof ScriptFunction) { - strict = ((ScriptFunction)callbackfn).isStrict(); - } else if (callbackfn instanceof JSObject && - ((JSObject)callbackfn).isFunction()) { - strict = ((JSObject)callbackfn).isStrictFunction(); - } else if (Bootstrap.isDynamicMethod(callbackfn) || Bootstrap.isFunctionalInterfaceObject(callbackfn)) { - strict = false; - } else { - throw typeError("not.a.function", ScriptRuntime.safeToString(callbackfn)); - } + final boolean strict = Bootstrap.isStrictCallable(callbackfn); // for non-strict callback, need to translate undefined thisArg to be global object thisArg = (thisArg == ScriptRuntime.UNDEFINED && !strict)? Context.getGlobal() : thisArg; diff --git a/src/jdk/nashorn/internal/runtime/arrays/LengthNotWritableFilter.java b/src/jdk/nashorn/internal/runtime/arrays/LengthNotWritableFilter.java new file mode 100644 index 00000000..945d80d2 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/arrays/LengthNotWritableFilter.java @@ -0,0 +1,198 @@ +package jdk.nashorn.internal.runtime.arrays; + +import java.util.Iterator; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; +import jdk.nashorn.internal.runtime.JSType; +import jdk.nashorn.internal.runtime.ScriptRuntime; + +/** + * Filter to use for ArrayData where the length is not writable. + * The default behavior is just to ignore {@link ArrayData#setLength} + */ +final class LengthNotWritableFilter extends ArrayFilter { + private final SortedMap<Long, Object> extraElements; //elements with index >= length + + /** + * Constructor + * @param underlying array + */ + LengthNotWritableFilter(final ArrayData underlying) { + this(underlying, new TreeMap<Long, Object>()); + } + + private LengthNotWritableFilter(final ArrayData underlying, final SortedMap<Long, Object> extraElements) { + super(underlying); + this.extraElements = extraElements; + } + + @Override + public ArrayData copy() { + return new LengthNotWritableFilter(underlying.copy(), new TreeMap<>(extraElements)); + } + + @Override + public boolean has(final int index) { + return super.has(index) || extraElements.containsKey((long)index); + } + + /** + * Set the length of the data array + * + * @param length the new length for the data array + */ + @Override + public void setLength(final long length) { + //empty - setting length for a LengthNotWritableFilter is always a nop + } + + @Override + public ArrayData ensure(final long index) { + return this; + } + + @Override + public ArrayData slice(final long from, final long to) { + //return array[from...to), or array[from...length] if undefined, in this case not as we are an ArrayData + return new LengthNotWritableFilter(underlying.slice(from, to), extraElements.subMap(from, to)); + } + + private boolean checkAdd(final long index, final Object value) { + if (index >= length()) { + extraElements.put(index, value); + return true; + } + return false; + } + + private Object get(final long index) { + final Object obj = extraElements.get(index); + if (obj == null) { + return ScriptRuntime.UNDEFINED; + } + return obj; + } + + @Override + public int getInt(final int index) { + if (index >= length()) { + return JSType.toInt32(get(index)); + } + return underlying.getInt(index); + } + + @Override + public int getIntOptimistic(final int index, final int programPoint) { + if (index >= length()) { + return JSType.toInt32Optimistic(get(index), programPoint); + } + return underlying.getIntOptimistic(index, programPoint); + } + + @Override + public long getLong(final int index) { + if (index >= length()) { + return JSType.toLong(get(index)); + } + return underlying.getLong(index); + } + + @Override + public long getLongOptimistic(final int index, final int programPoint) { + if (index >= length()) { + return JSType.toLongOptimistic(get(index), programPoint); + } + return underlying.getLongOptimistic(index, programPoint); + } + + @Override + public double getDouble(final int index) { + if (index >= length()) { + return JSType.toNumber(get(index)); + } + return underlying.getDouble(index); + } + + @Override + public double getDoubleOptimistic(final int index, final int programPoint) { + if (index >= length()) { + return JSType.toNumberOptimistic(get(index), programPoint); + } + return underlying.getDoubleOptimistic(index, programPoint); + } + + @Override + public Object getObject(final int index) { + if (index >= length()) { + return get(index); + } + return underlying.getObject(index); + } + + @Override + public ArrayData set(final int index, final Object value, final boolean strict) { + if (checkAdd(index, value)) { + return this; + } + underlying = underlying.set(index, value, strict); + return this; + } + + @Override + public ArrayData set(final int index, final int value, final boolean strict) { + if (checkAdd(index, value)) { + return this; + } + underlying = underlying.set(index, value, strict); + return this; + } + + @Override + public ArrayData set(final int index, final long value, final boolean strict) { + if (checkAdd(index, value)) { + return this; + } + underlying = underlying.set(index, value, strict); + return this; + } + + @Override + public ArrayData set(final int index, final double value, final boolean strict) { + if (checkAdd(index, value)) { + return this; + } + underlying = underlying.set(index, value, strict); + return this; + } + + @Override + public ArrayData delete(final int index) { + extraElements.remove(index); + underlying = underlying.delete(index); + return this; + } + + @Override + public ArrayData delete(final long fromIndex, final long toIndex) { + for (final Iterator<Long> iter = extraElements.keySet().iterator(); iter.hasNext();) { + final long next = iter.next(); + if (next >= fromIndex && next <= toIndex) { + iter.remove(); + } + if (next > toIndex) { //ordering guaranteed because TreeSet + break; + } + } + underlying = underlying.delete(fromIndex, toIndex); + return this; + } + + @Override + public Iterator<Long> indexIterator() { + final List<Long> keys = computeIteratorKeys(); + keys.addAll(extraElements.keySet()); //even if they are outside length this is fine + return keys.iterator(); + } + +} diff --git a/src/jdk/nashorn/internal/runtime/arrays/LongArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/LongArrayData.java index f41ee15a..0437cdfe 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/LongArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/LongArrayData.java @@ -27,7 +27,6 @@ package jdk.nashorn.internal.runtime.arrays; import static jdk.nashorn.internal.codegen.CompilerConstants.specialCall; import static jdk.nashorn.internal.lookup.Lookup.MH; - import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.Arrays; @@ -52,17 +51,32 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen LongArrayData(final long array[], final int length) { super(length); assert array.length >= length; - this.array = array; + this.array = array; } @Override - public Class<?> getElementType() { + public final Class<?> getElementType() { return long.class; } @Override - public ArrayData copy() { - return new LongArrayData(array.clone(), (int)length); + public final Class<?> getBoxedElementType() { + return Long.class; + } + + @Override + public final ContinuousArrayData widest(final ContinuousArrayData otherData) { + return otherData instanceof IntElements ? this : otherData; + } + + @Override + public final int getElementWeight() { + return 2; + } + + @Override + public LongArrayData copy() { + return new LongArrayData(array.clone(), (int)length()); } @Override @@ -71,10 +85,11 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen } private Object[] toObjectArray(final boolean trim) { - assert length <= array.length : "length exceeds internal array size"; - final Object[] oarray = new Object[trim ? (int)length : array.length]; + assert length() <= array.length : "length exceeds internal array size"; + final int len = (int)length(); + final Object[] oarray = new Object[trim ? len : array.length]; - for (int index = 0; index < length; index++) { + for (int index = 0; index < len; index++) { oarray[index] = Long.valueOf(array[index]); } @@ -84,16 +99,18 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public Object asArrayOfType(final Class<?> componentType) { if (componentType == long.class) { - return array.length == length ? array.clone() : Arrays.copyOf(array, (int)length); + final int len = (int)length(); + return array.length == len ? array.clone() : Arrays.copyOf(array, len); } return super.asArrayOfType(componentType); } private double[] toDoubleArray() { - assert length <= array.length : "length exceeds internal array size"; + assert length() <= array.length : "length exceeds internal array size"; + final int len = (int)length(); final double[] darray = new double[array.length]; - for (int index = 0; index < length; index++) { + for (int index = 0; index < len; index++) { darray[index] = array[index]; } @@ -101,11 +118,11 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen } @Override - public ArrayData convert(final Class<?> type) { + public ContinuousArrayData convert(final Class<?> type) { if (type == Integer.class || type == Long.class) { return this; } - final int len = (int)length; + final int len = (int)length(); if (type == Double.class) { return new NumberArrayData(toDoubleArray(), len); } @@ -119,7 +136,7 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public ArrayData shiftRight(final int by) { - final ArrayData newData = ensure(by + length - 1); + final ArrayData newData = ensure(by + length() - 1); if (newData != this) { newData.shiftRight(by); return newData; @@ -145,8 +162,7 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public ArrayData shrink(final long newLength) { - Arrays.fill(array, (int) newLength, array.length, 0); - + Arrays.fill(array, (int)newLength, array.length, 0L); return this; } @@ -165,14 +181,14 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public ArrayData set(final int index, final int value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @Override public ArrayData set(final int index, final long value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @@ -180,7 +196,7 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen public ArrayData set(final int index, final double value, final boolean strict) { if (JSType.isRepresentableAsLong(value)) { array[index] = (long)value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } return convert(Double.class).set(index, value, strict); @@ -251,7 +267,7 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public boolean has(final int index) { - return 0 <= index && index < length; + return 0 <= index && index < length(); } @Override @@ -266,11 +282,12 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public Object pop() { - if (length == 0) { + final int len = (int)length(); + if (len == 0) { return ScriptRuntime.UNDEFINED; } - final int newLength = (int)length - 1; + final int newLength = len - 1; final long elem = array[newLength]; array[newLength] = 0; setLength(newLength); @@ -280,14 +297,14 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public ArrayData slice(final long from, final long to) { - final long start = from < 0 ? from + length : from; + final long start = from < 0 ? from + length() : from; final long newLength = to - start; return new LongArrayData(Arrays.copyOfRange(array, (int)from, (int)to), (int)newLength); } @Override public final ArrayData push(final boolean strict, final long item) { - final long len = length; + final long len = length(); final ArrayData newData = ensure(len); if (newData == this) { array[(int)len] = item; @@ -298,7 +315,7 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public ArrayData fastSplice(final int start, final int removed, final int added) throws UnsupportedOperationException { - final long oldLength = length; + final long oldLength = length(); final long newLength = oldLength - removed + added; if (newLength > SparseArrayData.MAX_DENSE_LENGTH && newLength > array.length) { throw new UnsupportedOperationException(); @@ -331,20 +348,20 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen @Override public long fastPush(final long arg) { - final int len = (int)length; + final int len = (int)length(); if (len == array.length) { array = Arrays.copyOf(array, nextSize(len)); } array[len] = arg; - return ++length; + return increaseLength(); } @Override public long fastPopLong() { - if (length == 0) { - throw new ClassCastException(); + if (length() == 0) { + throw new ClassCastException(); //undefined result } - final int newLength = (int)--length; + final int newLength = (int)decreaseLength(); final long elem = array[newLength]; array[newLength] = 0; return elem; @@ -359,4 +376,38 @@ final class LongArrayData extends ContinuousArrayData implements IntOrLongElemen public Object fastPopObject() { return fastPopLong(); } + + @Override + public ContinuousArrayData fastConcat(final ContinuousArrayData otherData) { + final int otherLength = (int)otherData.length(); + final int thisLength = (int)length(); + assert otherLength > 0 && thisLength > 0; + + final long[] otherArray = ((LongArrayData)otherData).array; + final int newLength = otherLength + thisLength; + final long[] newArray = new long[ArrayData.alignUp(newLength)]; + + System.arraycopy(array, 0, newArray, 0, thisLength); + System.arraycopy(otherArray, 0, newArray, thisLength, otherLength); + + return new LongArrayData(newArray, newLength); + } + + @Override + public String toString() { + assert length() <= array.length : length() + " > " + array.length; + + final StringBuilder sb = new StringBuilder(getClass().getSimpleName()). + append(": ["); + final int len = (int)length(); + for (int i = 0; i < len; i++) { + sb.append(array[i]).append('L'); //make sure L suffix is on elements, to discriminate this from IntArrayData.toString() + if (i + 1 < len) { + sb.append(", "); + } + } + sb.append(']'); + + return sb.toString(); + } } diff --git a/src/jdk/nashorn/internal/runtime/arrays/NoTypeArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/NoTypeArrayData.java deleted file mode 100644 index 143cd221..00000000 --- a/src/jdk/nashorn/internal/runtime/arrays/NoTypeArrayData.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.nashorn.internal.runtime.arrays; - -import java.lang.reflect.Array; -import jdk.nashorn.internal.runtime.ScriptRuntime; - -/** - * Place holding array data for non-array objects. Activates a true array when - * accessed. Should only exist as a singleton defined in ArrayData. - */ -final class NoTypeArrayData extends ArrayData { - NoTypeArrayData() { - super(0); - } - - NoTypeArrayData(final long length) { - super(length); - } - - @Override - public Object[] asObjectArray() { - return ScriptRuntime.EMPTY_ARRAY; - } - - @Override - public ArrayData copy() { - return new NoTypeArrayData(); - } - - @Override - public Object asArrayOfType(final Class<?> componentType) { - return Array.newInstance(componentType, 0); - } - - @Override - public ArrayData convert(final Class<?> type) { - final long len = length; - final ArrayData arrayData; - if (type == Long.class) { - arrayData = new LongArrayData(new long[ArrayData.nextSize((int)len)], (int)len); - } else if (type == Double.class) { - arrayData = new NumberArrayData(new double[ArrayData.nextSize((int)len)], (int)len); - } else if (type == Integer.class) { - arrayData = new IntArrayData(new int[ArrayData.nextSize((int)len)], (int)len); - } else { - assert !type.isPrimitive(); - arrayData = new ObjectArrayData(new Object[ArrayData.nextSize((int)len)], (int)len); - } - return length == 0 ? arrayData : new DeletedRangeArrayFilter(arrayData, 0, len - 1); - } - - @Override - public void shiftLeft(final int by) { - //empty - } - - @Override - public ArrayData shiftRight(final int by) { - return this; - } - - @Override - public ArrayData ensure(final long safeIndex) { - if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH) { - return new SparseArrayData(this, safeIndex + 1); - } - - // Don't trample the shared EMPTY_ARRAY. - if (length == 0) { - return new NoTypeArrayData(Math.max(safeIndex + 1, length)); - } - - setLength(Math.max(safeIndex + 1, length)); - return this; - } - - @Override - public ArrayData shrink(final long newLength) { - return this; - } - - @Override - public ArrayData set(final int index, final Object value, final boolean strict) { - ArrayData newData; - - if (value instanceof Double) { - newData = convert(Double.class); - } else if (value instanceof Long) { - newData = convert(Long.class); - } else if (value instanceof Integer) { - newData = convert(Integer.class); - } else { - assert !(value instanceof Number); - newData = convert(value == null ? Object.class : value.getClass()); - } - - return newData.set(index, value, strict); - } - - @Override - public ArrayData set(final int index, final int value, final boolean strict) { - final ArrayData newData = convert(Integer.class); - return newData.set(index, value, strict); - } - - @Override - public ArrayData set(final int index, final long value, final boolean strict) { - final ArrayData newData = convert(Long.class); - return newData.set(index, value, strict); - } - - @Override - public ArrayData set(final int index, final double value, final boolean strict) { - final ArrayData newData = convert(Double.class); - return newData.set(index, value, strict); - } - - @Override - public int getInt(final int index) { - throw new ArrayIndexOutOfBoundsException(index); - } - - @Override - public long getLong(final int index) { - throw new ArrayIndexOutOfBoundsException(index); - } - - @Override - public double getDouble(final int index) { - throw new ArrayIndexOutOfBoundsException(index); - } - - @Override - public Object getObject(final int index) { - throw new ArrayIndexOutOfBoundsException(index); - } - - @Override - public boolean has(final int index) { - return false; - } - - @Override - public ArrayData delete(final int index) { - return new DeletedRangeArrayFilter(this, index, index); - } - - @Override - public ArrayData delete(final long fromIndex, final long toIndex) { - return new DeletedRangeArrayFilter(this, fromIndex, toIndex); - } - - @Override - public Object pop() { - return ScriptRuntime.UNDEFINED; - } - - @Override - public ArrayData slice(final long from, final long to) { - return this; - } -} diff --git a/src/jdk/nashorn/internal/runtime/arrays/NonExtensibleArrayFilter.java b/src/jdk/nashorn/internal/runtime/arrays/NonExtensibleArrayFilter.java new file mode 100644 index 00000000..7e0f3c63 --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/arrays/NonExtensibleArrayFilter.java @@ -0,0 +1,68 @@ +package jdk.nashorn.internal.runtime.arrays; + +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; +import jdk.nashorn.internal.objects.Global; +import jdk.nashorn.internal.runtime.ScriptRuntime; + +/** + * Filter class that wrap arrays that have been tagged non extensible + */ +final class NonExtensibleArrayFilter extends ArrayFilter { + + /** + * Constructor + * @param underlying array + */ + NonExtensibleArrayFilter(final ArrayData underlying) { + super(underlying); + } + + @Override + public ArrayData copy() { + return new NonExtensibleArrayFilter(underlying.copy()); + } + + @Override + public ArrayData slice(final long from, final long to) { + return new NonExtensibleArrayFilter(underlying.slice(from, to)); + } + + private ArrayData extensionCheck(final boolean strict, final int index) { + if (!strict) { + return this; + } + throw typeError(Global.instance(), "object.non.extensible", String.valueOf(index), ScriptRuntime.safeToString(this)); + } + + @Override + public ArrayData set(final int index, final Object value, final boolean strict) { + if (has(index)) { + return underlying.set(index, value, strict); + } + return extensionCheck(strict, index); + } + + @Override + public ArrayData set(final int index, final int value, final boolean strict) { + if (has(index)) { + return underlying.set(index, value, strict); + } + return extensionCheck(strict, index); + } + + @Override + public ArrayData set(final int index, final long value, final boolean strict) { + if (has(index)) { + return underlying.set(index, value, strict); + } + return extensionCheck(strict, index); + } + + @Override + public ArrayData set(final int index, final double value, final boolean strict) { + if (has(index)) { + return underlying.set(index, value, strict); + } + return extensionCheck(strict, index); + } +} diff --git a/src/jdk/nashorn/internal/runtime/arrays/NumberArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/NumberArrayData.java index 2c57208f..2522b979 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/NumberArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/NumberArrayData.java @@ -28,7 +28,6 @@ package jdk.nashorn.internal.runtime.arrays; import static jdk.nashorn.internal.codegen.CompilerConstants.specialCall; import static jdk.nashorn.internal.lookup.Lookup.MH; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; - import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.Arrays; @@ -48,20 +47,35 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen * @param array an int array * @param length a length, not necessarily array.length */ - NumberArrayData(final double array[], final int length) { + NumberArrayData(final double[] array, final int length) { super(length); assert array.length >= length; - this.array = array; + this.array = array; } @Override - public Class<?> getElementType() { + public final Class<?> getElementType() { return double.class; } @Override - public ArrayData copy() { - return new NumberArrayData(array.clone(), (int)length); + public final Class<?> getBoxedElementType() { + return Double.class; + } + + @Override + public final int getElementWeight() { + return 3; + } + + @Override + public final ContinuousArrayData widest(final ContinuousArrayData otherData) { + return otherData instanceof IntOrLongElements ? this : otherData; + } + + @Override + public NumberArrayData copy() { + return new NumberArrayData(array.clone(), (int)length()); } @Override @@ -70,10 +84,11 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen } private Object[] toObjectArray(final boolean trim) { - assert length <= array.length : "length exceeds internal array size"; - final Object[] oarray = new Object[trim ? (int)length : array.length]; + assert length() <= array.length : "length exceeds internal array size"; + final int len = (int)length(); + final Object[] oarray = new Object[trim ? len : array.length]; - for (int index = 0; index < length; index++) { + for (int index = 0; index < len; index++) { oarray[index] = Double.valueOf(array[index]); } return oarray; @@ -81,16 +96,17 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public Object asArrayOfType(final Class<?> componentType) { - if(componentType == double.class) { - return array.length == length ? array.clone() : Arrays.copyOf(array, (int)length); + if (componentType == double.class) { + final int len = (int)length(); + return array.length == len ? array.clone() : Arrays.copyOf(array, len); } return super.asArrayOfType(componentType); } @Override - public ArrayData convert(final Class<?> type) { + public ContinuousArrayData convert(final Class<?> type) { if (type != Double.class && type != Integer.class && type != Long.class) { - final int len = (int)length; + final int len = (int)length(); return new ObjectArrayData(toObjectArray(false), len); } return this; @@ -103,7 +119,7 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public ArrayData shiftRight(final int by) { - final ArrayData newData = ensure(by + length - 1); + final ArrayData newData = ensure(by + length() - 1); if (newData != this) { newData.shiftRight(by); return newData; @@ -129,7 +145,7 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public ArrayData shrink(final long newLength) { - Arrays.fill(array, (int) newLength, array.length, 0.0); + Arrays.fill(array, (int)newLength, array.length, 0.0); return this; } @@ -148,21 +164,21 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public ArrayData set(final int index, final int value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @Override public ArrayData set(final int index, final long value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @Override public ArrayData set(final int index, final double value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @@ -226,7 +242,7 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public boolean has(final int index) { - return 0 <= index && index < length; + return 0 <= index && index < length(); } @Override @@ -241,11 +257,12 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public Object pop() { - if (length == 0) { + final int len = (int)length(); + if (len == 0) { return UNDEFINED; } - final int newLength = (int)length - 1; + final int newLength = len - 1; final double elem = array[newLength]; array[newLength] = 0; setLength(newLength); @@ -254,14 +271,14 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public ArrayData slice(final long from, final long to) { - final long start = from < 0 ? from + length : from; + final long start = from < 0 ? from + length() : from; final long newLength = to - start; return new NumberArrayData(Arrays.copyOfRange(array, (int)from, (int)to), (int)newLength); } @Override public final ArrayData push(final boolean strict, final double item) { - final long len = length; + final long len = length(); final ArrayData newData = ensure(len); if (newData == this) { array[(int)len] = item; @@ -272,7 +289,7 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public ArrayData fastSplice(final int start, final int removed, final int added) throws UnsupportedOperationException { - final long oldLength = length; + final long oldLength = length(); final long newLength = oldLength - removed + added; if (newLength > SparseArrayData.MAX_DENSE_LENGTH && newLength > array.length) { throw new UnsupportedOperationException(); @@ -310,21 +327,21 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen @Override public long fastPush(final double arg) { - final int len = (int)length; + final int len = (int)length(); if (len == array.length) { //note that fastpush never creates spares arrays, there is nothing to gain by that - it will just use even more memory array = Arrays.copyOf(array, nextSize(len)); } array[len] = arg; - return ++length; + return increaseLength(); } @Override public double fastPopDouble() { - if (length == 0) { + if (length() == 0) { throw new ClassCastException(); } - final int newLength = (int)--length; + final int newLength = (int)decreaseLength(); final double elem = array[newLength]; array[newLength] = 0; return elem; @@ -334,4 +351,26 @@ final class NumberArrayData extends ContinuousArrayData implements NumericElemen public Object fastPopObject() { return fastPopDouble(); } + + @Override + public ContinuousArrayData fastConcat(final ContinuousArrayData otherData) { + final int otherLength = (int)otherData.length(); + final int thisLength = (int)length(); + assert otherLength > 0 && thisLength > 0; + + final double[] otherArray = ((NumberArrayData)otherData).array; + final int newLength = otherLength + thisLength; + final double[] newArray = new double[ArrayData.alignUp(newLength)]; + + System.arraycopy(array, 0, newArray, 0, thisLength); + System.arraycopy(otherArray, 0, newArray, thisLength, otherLength); + + return new NumberArrayData(newArray, newLength); + } + + @Override + public String toString() { + assert length() <= array.length : length() + " > " + array.length; + return getClass().getSimpleName() + ':' + Arrays.toString(Arrays.copyOf(array, (int)length())); + } } diff --git a/src/jdk/nashorn/internal/runtime/arrays/NumericElements.java b/src/jdk/nashorn/internal/runtime/arrays/NumericElements.java index ad940e2a..cb87ea91 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/NumericElements.java +++ b/src/jdk/nashorn/internal/runtime/arrays/NumericElements.java @@ -30,6 +30,6 @@ package jdk.nashorn.internal.runtime.arrays; * Used for type checks that throw ClassCastExceptions and force relinks * for fast NativeArray specializations of builtin methods */ -public interface NumericElements { +public interface NumericElements extends AnyElements { //empty } diff --git a/src/jdk/nashorn/internal/runtime/arrays/ObjectArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/ObjectArrayData.java index 379ba6e4..ebaa3d6d 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/ObjectArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/ObjectArrayData.java @@ -26,7 +26,6 @@ package jdk.nashorn.internal.runtime.arrays; import static jdk.nashorn.internal.codegen.CompilerConstants.specialCall; - import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.Arrays; @@ -37,7 +36,7 @@ import jdk.nashorn.internal.runtime.ScriptRuntime; * Implementation of {@link ArrayData} as soon as an Object has been * written to the array */ -final class ObjectArrayData extends ContinuousArrayData { +final class ObjectArrayData extends ContinuousArrayData implements AnyElements { /** * The wrapped array @@ -49,29 +48,44 @@ final class ObjectArrayData extends ContinuousArrayData { * @param array an int array * @param length a length, not necessarily array.length */ - ObjectArrayData(final Object array[], final int length) { + ObjectArrayData(final Object[] array, final int length) { super(length); assert array.length >= length; this.array = array; } @Override - public Class<?> getElementType() { + public final Class<?> getElementType() { return Object.class; } @Override - public ArrayData copy() { - return new ObjectArrayData(array.clone(), (int)length); + public final Class<?> getBoxedElementType() { + return getElementType(); + } + + @Override + public final int getElementWeight() { + return 4; + } + + @Override + public final ContinuousArrayData widest(final ContinuousArrayData otherData) { + return otherData instanceof NumericElements ? this : otherData; + } + + @Override + public ObjectArrayData copy() { + return new ObjectArrayData(array.clone(), (int)length()); } @Override public Object[] asObjectArray() { - return array.length == length ? array.clone() : asObjectArrayCopy(); + return array.length == length() ? array.clone() : asObjectArrayCopy(); } private Object[] asObjectArrayCopy() { - final long len = length; + final long len = length(); assert len <= Integer.MAX_VALUE; final Object[] copy = new Object[(int)len]; System.arraycopy(array, 0, copy, 0, (int)len); @@ -79,7 +93,7 @@ final class ObjectArrayData extends ContinuousArrayData { } @Override - public ArrayData convert(final Class<?> type) { + public ObjectArrayData convert(final Class<?> type) { return this; } @@ -90,7 +104,7 @@ final class ObjectArrayData extends ContinuousArrayData { @Override public ArrayData shiftRight(final int by) { - final ArrayData newData = ensure(by + length - 1); + final ArrayData newData = ensure(by + length() - 1); if (newData != this) { newData.shiftRight(by); return newData; @@ -122,28 +136,28 @@ final class ObjectArrayData extends ContinuousArrayData { @Override public ArrayData set(final int index, final Object value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @Override public ArrayData set(final int index, final int value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @Override public ArrayData set(final int index, final long value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @Override public ArrayData set(final int index, final double value, final boolean strict) { array[index] = value; - setLength(Math.max(index + 1, length)); + setLength(Math.max(index + 1, length())); return this; } @@ -216,7 +230,7 @@ final class ObjectArrayData extends ContinuousArrayData { @Override public boolean has(final int index) { - return 0 <= index && index < length; + return 0 <= index && index < length(); } @Override @@ -248,20 +262,20 @@ final class ObjectArrayData extends ContinuousArrayData { @Override public long fastPush(final Object arg) { - final int len = (int)length; + final int len = (int)length(); if (len == array.length) { array = Arrays.copyOf(array, nextSize(len)); } array[len] = arg; - return ++length; + return increaseLength(); } @Override public Object fastPopObject() { - if (length == 0) { + if (length() == 0) { return ScriptRuntime.UNDEFINED; } - final int newLength = (int)--length; + final int newLength = (int)decreaseLength(); final Object elem = array[newLength]; array[newLength] = ScriptRuntime.EMPTY; return elem; @@ -269,11 +283,11 @@ final class ObjectArrayData extends ContinuousArrayData { @Override public Object pop() { - if (length == 0) { + if (length() == 0) { return ScriptRuntime.UNDEFINED; } - final int newLength = (int)length - 1; + final int newLength = (int)length() - 1; final Object elem = array[newLength]; setEmpty(newLength); setLength(newLength); @@ -282,14 +296,14 @@ final class ObjectArrayData extends ContinuousArrayData { @Override public ArrayData slice(final long from, final long to) { - final long start = from < 0 ? from + length : from; + final long start = from < 0 ? from + length() : from; final long newLength = to - start; return new ObjectArrayData(Arrays.copyOfRange(array, (int)from, (int)to), (int)newLength); } @Override public ArrayData push(final boolean strict, final Object item) { - final long len = length; + final long len = length(); final ArrayData newData = ensure(len); if (newData == this) { array[(int)len] = item; @@ -300,7 +314,7 @@ final class ObjectArrayData extends ContinuousArrayData { @Override public ArrayData fastSplice(final int start, final int removed, final int added) throws UnsupportedOperationException { - final long oldLength = length; + final long oldLength = length(); final long newLength = oldLength - removed + added; if (newLength > SparseArrayData.MAX_DENSE_LENGTH && newLength > array.length) { throw new UnsupportedOperationException(); @@ -325,4 +339,26 @@ final class ObjectArrayData extends ContinuousArrayData { return returnValue; } + + @Override + public ContinuousArrayData fastConcat(final ContinuousArrayData otherData) { + final int otherLength = (int)otherData.length(); + final int thisLength = (int)length(); + assert otherLength > 0 && thisLength > 0; + + final Object[] otherArray = ((ObjectArrayData)otherData).array; + final int newLength = otherLength + thisLength; + final Object[] newArray = new Object[ArrayData.alignUp(newLength)]; + + System.arraycopy(array, 0, newArray, 0, thisLength); + System.arraycopy(otherArray, 0, newArray, thisLength, otherLength); + + return new ObjectArrayData(newArray, newLength); + } + + @Override + public String toString() { + assert length() <= array.length : length() + " > " + array.length; + return getClass().getSimpleName() + ':' + Arrays.toString(Arrays.copyOf(array, (int)length())); + } } diff --git a/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java index ef473451..ff569d44 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java @@ -36,7 +36,7 @@ import jdk.nashorn.internal.runtime.ScriptRuntime; * Handle arrays where the index is very large. */ class SparseArrayData extends ArrayData { - static final long MAX_DENSE_LENGTH = 16 * 512 * 1024; + static final int MAX_DENSE_LENGTH = 8 * 1024 * 1024; /** Underlying array. */ private ArrayData underlying; @@ -53,21 +53,21 @@ class SparseArrayData extends ArrayData { SparseArrayData(final ArrayData underlying, final long length, final TreeMap<Long, Object> sparseMap) { super(length); - assert underlying.length <= length; + assert underlying.length() <= length; this.underlying = underlying; - this.maxDenseLength = Math.max(MAX_DENSE_LENGTH, underlying.length); + this.maxDenseLength = Math.max(MAX_DENSE_LENGTH, underlying.length()); this.sparseMap = sparseMap; } @Override public ArrayData copy() { - return new SparseArrayData(underlying.copy(), length, new TreeMap<>(sparseMap)); + return new SparseArrayData(underlying.copy(), length(), new TreeMap<>(sparseMap)); } @Override public Object[] asObjectArray() { - final int len = (int)Math.min(length, Integer.MAX_VALUE); - final int underlyingLength = (int)Math.min(len, underlying.length); + final int len = (int)Math.min(length(), Integer.MAX_VALUE); + final int underlyingLength = (int)Math.min(len, underlying.length()); final Object[] objArray = new Object[len]; for (int i = 0; i < underlyingLength; i++) { @@ -104,14 +104,15 @@ class SparseArrayData extends ArrayData { } sparseMap = newSparseMap; - setLength(Math.max(length - by, 0)); + setLength(Math.max(length() - by, 0)); } @Override public ArrayData shiftRight(final int by) { final TreeMap<Long, Object> newSparseMap = new TreeMap<>(); - if (underlying.length + by > maxDenseLength) { - for (long i = maxDenseLength - by; i < underlying.length; i++) { + final long len = underlying.length(); + if (len + by > maxDenseLength) { + for (long i = maxDenseLength - by; i < len; i++) { if (underlying.has((int) i)) { newSparseMap.put(Long.valueOf(i + by), underlying.getObject((int) i)); } @@ -127,23 +128,23 @@ class SparseArrayData extends ArrayData { } sparseMap = newSparseMap; - setLength(length + by); + setLength(length() + by); return this; } @Override public ArrayData ensure(final long safeIndex) { - if (safeIndex < maxDenseLength && underlying.length <= safeIndex) { + if (safeIndex < maxDenseLength && underlying.length() <= safeIndex) { underlying = underlying.ensure(safeIndex); } - setLength(Math.max(safeIndex + 1, length)); + setLength(Math.max(safeIndex + 1, length())); return this; } @Override public ArrayData shrink(final long newLength) { - if (newLength < underlying.length) { + if (newLength < underlying.length()) { underlying = underlying.shrink(newLength); underlying.setLength(newLength); sparseMap.clear(); @@ -160,11 +161,11 @@ class SparseArrayData extends ArrayData { if (index >= 0 && index < maxDenseLength) { ensure(index); underlying = underlying.set(index, value, strict); - setLength(Math.max(underlying.length, length)); + setLength(Math.max(underlying.length(), length())); } else { final Long longIndex = indexToKey(index); sparseMap.put(longIndex, value); - setLength(Math.max(longIndex + 1, length)); + setLength(Math.max(longIndex + 1, length())); } return this; @@ -175,11 +176,11 @@ class SparseArrayData extends ArrayData { if (index >= 0 && index < maxDenseLength) { ensure(index); underlying = underlying.set(index, value, strict); - setLength(Math.max(underlying.length, length)); + setLength(Math.max(underlying.length(), length())); } else { final Long longIndex = indexToKey(index); sparseMap.put(longIndex, value); - setLength(Math.max(longIndex + 1, length)); + setLength(Math.max(longIndex + 1, length())); } return this; } @@ -189,11 +190,11 @@ class SparseArrayData extends ArrayData { if (index >= 0 && index < maxDenseLength) { ensure(index); underlying = underlying.set(index, value, strict); - setLength(Math.max(underlying.length, length)); + setLength(Math.max(underlying.length(), length())); } else { final Long longIndex = indexToKey(index); sparseMap.put(longIndex, value); - setLength(Math.max(longIndex + 1, length)); + setLength(Math.max(longIndex + 1, length())); } return this; } @@ -203,11 +204,11 @@ class SparseArrayData extends ArrayData { if (index >= 0 && index < maxDenseLength) { ensure(index); underlying = underlying.set(index, value, strict); - setLength(Math.max(underlying.length, length)); + setLength(Math.max(underlying.length(), length())); } else { final Long longIndex = indexToKey(index); sparseMap.put(longIndex, value); - setLength(Math.max(longIndex + 1, length)); + setLength(Math.max(longIndex + 1, length())); } return this; } @@ -294,7 +295,7 @@ class SparseArrayData extends ArrayData { @Override public boolean has(final int index) { if (index >= 0 && index < maxDenseLength) { - return index < underlying.length && underlying.has(index); + return index < underlying.length() && underlying.has(index); } return sparseMap.containsKey(indexToKey(index)); @@ -303,7 +304,7 @@ class SparseArrayData extends ArrayData { @Override public ArrayData delete(final int index) { if (index >= 0 && index < maxDenseLength) { - if (index < underlying.length) { + if (index < underlying.length()) { underlying = underlying.delete(index); } } else { @@ -315,8 +316,8 @@ class SparseArrayData extends ArrayData { @Override public ArrayData delete(final long fromIndex, final long toIndex) { - if (fromIndex < maxDenseLength && fromIndex < underlying.length) { - underlying = underlying.delete(fromIndex, Math.min(toIndex, underlying.length - 1)); + if (fromIndex < maxDenseLength && fromIndex < underlying.length()) { + underlying = underlying.delete(fromIndex, Math.min(toIndex, underlying.length() - 1)); } if (toIndex >= maxDenseLength) { sparseMap.subMap(fromIndex, true, toIndex, true).clear(); @@ -336,30 +337,34 @@ class SparseArrayData extends ArrayData { @Override public Object pop() { - if (length == 0) { + final long len = length(); + final long underlyingLen = underlying.length(); + if (len == 0) { return ScriptRuntime.UNDEFINED; } - if (length == underlying.length) { + if (len == underlyingLen) { final Object result = underlying.pop(); - setLength(underlying.length); + setLength(underlying.length()); return result; } - setLength(length - 1); - final Long key = Long.valueOf(length); + setLength(len - 1); + final Long key = Long.valueOf(len - 1); return sparseMap.containsKey(key) ? sparseMap.remove(key) : ScriptRuntime.UNDEFINED; } @Override public ArrayData slice(final long from, final long to) { - assert to <= length; - final long start = from < 0 ? (from + length) : from; + assert to <= length(); + final long start = from < 0 ? (from + length()) : from; final long newLength = to - start; + final long underlyingLength = underlying.length(); + if (start >= 0 && to <= maxDenseLength) { - if (newLength <= underlying.length) { + if (newLength <= underlyingLength) { return underlying.slice(from, to); } - return underlying.slice(from, to).ensure(newLength - 1).delete(underlying.length, newLength); + return underlying.slice(from, to).ensure(newLength - 1).delete(underlyingLength, newLength); } ArrayData sliced = EMPTY_ARRAY; @@ -369,13 +374,13 @@ class SparseArrayData extends ArrayData { sliced = sliced.set((int)(i - start), getObject((int)i), false); } } - assert sliced.length == newLength; + assert sliced.length() == newLength; return sliced; } @Override public long nextIndex(final long index) { - if (index < underlying.length - 1) { + if (index < underlying.length() - 1) { return underlying.nextIndex(index); } @@ -383,6 +388,7 @@ class SparseArrayData extends ArrayData { if (nextKey != null) { return nextKey; } - return length; + + return length(); } } diff --git a/src/jdk/nashorn/internal/runtime/arrays/TypedArrayData.java b/src/jdk/nashorn/internal/runtime/arrays/TypedArrayData.java index 428678d0..40b3210b 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/TypedArrayData.java +++ b/src/jdk/nashorn/internal/runtime/arrays/TypedArrayData.java @@ -58,7 +58,7 @@ public abstract class TypedArrayData<T extends Buffer> extends ContinuousArrayDa * @return element length */ public final int getElementLength() { - return (int)length; + return (int)length(); } /** @@ -88,7 +88,7 @@ public abstract class TypedArrayData<T extends Buffer> extends ContinuousArrayDa } @Override - public ArrayData copy() { + public TypedArrayData<T> copy() { throw new UnsupportedOperationException(); } @@ -119,7 +119,7 @@ public abstract class TypedArrayData<T extends Buffer> extends ContinuousArrayDa @Override public final boolean has(final int index) { - return 0 <= index && index < length; + return 0 <= index && index < length(); } @Override @@ -133,7 +133,7 @@ public abstract class TypedArrayData<T extends Buffer> extends ContinuousArrayDa } @Override - public ArrayData convert(final Class<?> type) { + public TypedArrayData<T> convert(final Class<?> type) { throw new UnsupportedOperationException(); } diff --git a/src/jdk/nashorn/internal/runtime/arrays/UndefinedArrayFilter.java b/src/jdk/nashorn/internal/runtime/arrays/UndefinedArrayFilter.java index f744aacd..9865dcbb 100644 --- a/src/jdk/nashorn/internal/runtime/arrays/UndefinedArrayFilter.java +++ b/src/jdk/nashorn/internal/runtime/arrays/UndefinedArrayFilter.java @@ -39,8 +39,7 @@ final class UndefinedArrayFilter extends ArrayFilter { UndefinedArrayFilter(final ArrayData underlying) { super(underlying); - - this.undefined = new BitVector(underlying.length); + this.undefined = new BitVector(underlying.length()); } @Override @@ -80,25 +79,24 @@ final class UndefinedArrayFilter extends ArrayFilter { @Override public void shiftLeft(final int by) { super.shiftLeft(by); - undefined.shiftLeft(by, length); + undefined.shiftLeft(by, length()); } @Override public ArrayData shiftRight(final int by) { super.shiftRight(by); - undefined.shiftRight(by, length); - + undefined.shiftRight(by, length()); return this; } @Override public ArrayData ensure(final long safeIndex) { - if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH && safeIndex >= length) { + if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH && safeIndex >= length()) { return new SparseArrayData(this, safeIndex + 1); } super.ensure(safeIndex); - undefined.resize(length); + undefined.resize(length()); return this; } @@ -106,8 +104,7 @@ final class UndefinedArrayFilter extends ArrayFilter { @Override public ArrayData shrink(final long newLength) { super.shrink(newLength); - undefined.resize(length); - + undefined.resize(length()); return this; } @@ -216,7 +213,7 @@ final class UndefinedArrayFilter extends ArrayFilter { @Override public Object pop() { - final long index = length - 1; + final long index = length() - 1; if (super.has((int)index)) { final boolean isUndefined = undefined.isSet(index); @@ -233,7 +230,7 @@ final class UndefinedArrayFilter extends ArrayFilter { final ArrayData newArray = underlying.slice(from, to); final UndefinedArrayFilter newFilter = new UndefinedArrayFilter(newArray); newFilter.getUndefined().copy(undefined); - newFilter.getUndefined().shiftLeft(from, newFilter.length); + newFilter.getUndefined().shiftLeft(from, newFilter.length()); return newFilter; } diff --git a/src/jdk/nashorn/internal/runtime/events/RecompilationEvent.java b/src/jdk/nashorn/internal/runtime/events/RecompilationEvent.java index bdc73bfe..8b2ff471 100644 --- a/src/jdk/nashorn/internal/runtime/events/RecompilationEvent.java +++ b/src/jdk/nashorn/internal/runtime/events/RecompilationEvent.java @@ -47,10 +47,9 @@ public final class RecompilationEvent extends RuntimeEvent<RewriteException> { * @param level logging level * @param rewriteException rewriteException wrapped by this RuntimEvent * @param returnValue rewriteException return value - as we don't want to make - * {@link RewriteException#getReturnValueNonDestructive()} public, we pass it as + * {@code RewriteException.getReturnValueNonDestructive()} public, we pass it as * an extra parameter, rather than querying the getter from another package. */ - @SuppressWarnings("javadoc") public RecompilationEvent(final Level level, final RewriteException rewriteException, final Object returnValue) { super(level, rewriteException); assert Context.getContext().getLogger(RecompilableScriptFunctionData.class).isEnabled() : diff --git a/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java b/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java index 67dd88e2..cd4dd3b7 100644 --- a/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java +++ b/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java @@ -26,6 +26,7 @@ package jdk.nashorn.internal.runtime.linker; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import java.lang.invoke.CallSite; import java.lang.invoke.ConstantCallSite; @@ -42,12 +43,16 @@ import jdk.internal.dynalink.beans.StaticClass; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; import jdk.internal.dynalink.linker.LinkerServices; +import jdk.internal.dynalink.linker.MethodTypeConversionStrategy; +import jdk.internal.dynalink.support.TypeUtilities; import jdk.nashorn.api.scripting.JSObject; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.codegen.ObjectClassGenerator; import jdk.nashorn.internal.codegen.RuntimeCallSite; import jdk.nashorn.internal.lookup.MethodHandleFactory; import jdk.nashorn.internal.lookup.MethodHandleFunctionality; +import jdk.nashorn.internal.objects.ScriptFunctionImpl; +import jdk.nashorn.internal.runtime.ECMAException; import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.OptimisticReturnFilters; import jdk.nashorn.internal.runtime.ScriptFunction; @@ -63,6 +68,8 @@ public final class Bootstrap { private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality(); + private static final MethodHandle VOID_TO_OBJECT = MH.constant(Object.class, ScriptRuntime.UNDEFINED); + /** * The default dynalink relink threshold for megamorphisism is 8. In the case * of object fields only, it is fine. However, with dual fields, in order to get @@ -92,7 +99,7 @@ public final class Bootstrap { new NashornLinker(), new NashornPrimitiveLinker(), new NashornStaticClassLinker(), - new BoundDynamicMethodLinker(), + new BoundCallableLinker(), new JavaSuperAdapterLinker(), new JSObjectLinker(nashornBeansLinker), new BrowserJSObjectLinker(nashornBeansLinker), @@ -106,6 +113,12 @@ public final class Bootstrap { return OptimisticReturnFilters.filterOptimisticReturnValue(inv, desc).asType(linkerServices, desc.getMethodType()); } }); + factory.setAutoConversionStrategy(new MethodTypeConversionStrategy() { + @Override + public MethodHandle asType(final MethodHandle target, final MethodType newType) { + return unboxReturnType(target, newType); + } + }); final int relinkThreshold = Options.getIntProperty("nashorn.unstable.relink.threshold", NASHORN_DEFAULT_UNSTABLE_RELINK_THRESHOLD); if (relinkThreshold > -1) { factory.setUnstableRelinkThreshold(relinkThreshold); @@ -128,19 +141,47 @@ public final class Bootstrap { } return obj instanceof ScriptFunction || - ((obj instanceof JSObject) && ((JSObject)obj).isFunction()) || - isDynamicMethod(obj) || + isJSObjectFunction(obj) || + BeansLinker.isDynamicMethod(obj) || + obj instanceof BoundCallable || isFunctionalInterfaceObject(obj) || obj instanceof StaticClass; } /** + * Returns true if the given object is a strict callable + * @param callable the callable object to be checked for strictness + * @return true if the obj is a strict callable, false if it is a non-strict callable. + * @throws ECMAException with {@code TypeError} if the object is not a callable. + */ + public static boolean isStrictCallable(final Object callable) { + if (callable instanceof ScriptFunction) { + return ((ScriptFunction)callable).isStrict(); + } else if (isJSObjectFunction(callable)) { + return ((JSObject)callable).isStrictFunction(); + } else if (callable instanceof BoundCallable) { + return isStrictCallable(((BoundCallable)callable).getCallable()); + } else if (BeansLinker.isDynamicMethod(callable) || callable instanceof StaticClass) { + return false; + } + throw notFunction(callable); + } + + private static ECMAException notFunction(final Object obj) { + return typeError("not.a.function", ScriptRuntime.safeToString(obj)); + } + + private static boolean isJSObjectFunction(final Object obj) { + return obj instanceof JSObject && ((JSObject)obj).isFunction(); + } + + /** * Returns if the given object is a dynalink Dynamic method * @param obj object to be checked * @return true if the obj is a dynamic method */ public static boolean isDynamicMethod(final Object obj) { - return obj instanceof BoundDynamicMethod || BeansLinker.isDynamicMethod(obj); + return BeansLinker.isDynamicMethod(obj instanceof BoundCallable ? ((BoundCallable)obj).getCallable() : obj); } /** @@ -150,7 +191,7 @@ public final class Bootstrap { * @return true if the obj is an instance of @FunctionalInterface interface */ public static boolean isFunctionalInterfaceObject(final Object obj) { - return !JSType.isPrimitive(obj) && (NashornBottomLinker.getFunctionalInterfaceMethod(obj.getClass()) != null); + return !JSType.isPrimitive(obj) && (NashornBeansLinker.getFunctionalInterfaceMethod(obj.getClass()) != null); } /** @@ -337,6 +378,20 @@ public final class Bootstrap { /** * Returns a dynamic invoker for a specified dynamic operation using the public lookup. Similar to + * {@link #createDynamicInvoker(String, Class, Class...)} but with an additional parameter to + * set the call site flags of the dynamic invoker. + * @param opDesc Dynalink dynamic operation descriptor. + * @param flags the call site flags for the operation + * @param rtype the return type for the operation + * @param ptypes the parameter types for the operation + * @return MethodHandle for invoking the operation. + */ + public static MethodHandle createDynamicInvoker(final String opDesc, final int flags, final Class<?> rtype, final Class<?>... ptypes) { + return bootstrap(MethodHandles.publicLookup(), opDesc, MethodType.methodType(rtype, ptypes), flags).dynamicInvoker(); + } + + /** + * Returns a dynamic invoker for a specified dynamic operation using the public lookup. Similar to * {@link #createDynamicInvoker(String, Class, Class...)} but with return and parameter types composed into a * method type in the signature. See the discussion of that method for details. * @param opDesc Dynalink dynamic operation descriptor. @@ -348,14 +403,22 @@ public final class Bootstrap { } /** - * Binds a bean dynamic method (returned by invoking {@code dyn:getMethod} on an object linked with - * {@code BeansLinker} to a receiver. - * @param dynamicMethod the dynamic method to bind + * Binds any object Nashorn can use as a [[Callable]] to a receiver and optionally arguments. + * @param callable the callable to bind * @param boundThis the bound "this" value. - * @return a bound dynamic method. + * @param boundArgs the bound arguments. Can be either null or empty array to signify no arguments are bound. + * @return a bound callable. + * @throws ECMAException with {@code TypeError} if the object is not a callable. */ - public static Object bindDynamicMethod(final Object dynamicMethod, final Object boundThis) { - return new BoundDynamicMethod(dynamicMethod, boundThis); + public static Object bindCallable(final Object callable, final Object boundThis, final Object[] boundArgs) { + if (callable instanceof ScriptFunctionImpl) { + return ((ScriptFunctionImpl)callable).makeBoundFunction(boundThis, boundArgs); + } else if (callable instanceof BoundCallable) { + return ((BoundCallable)callable).bind(boundArgs); + } else if (isCallable(callable)) { + return new BoundCallable(callable, boundThis, boundArgs); + } + throw notFunction(callable); } /** @@ -406,4 +469,31 @@ public final class Bootstrap { static GuardedInvocation asTypeSafeReturn(final GuardedInvocation inv, final LinkerServices linkerServices, final CallSiteDescriptor desc) { return inv == null ? null : inv.asTypeSafeReturn(linkerServices, desc.getMethodType()); } + + /** + * Adapts the return type of the method handle with {@code explicitCastArguments} when it is an unboxing + * conversion. This will ensure that nulls are unwrapped to false or 0. + * @param target the target method handle + * @param newType the desired new type. Note that this method does not adapt the method handle completely to the + * new type, it only adapts the return type; this is allowed as per + * {@link DynamicLinkerFactory#setAutoConversionStrategy(MethodTypeConversionStrategy)}, which is what this method + * is used for. + * @return the method handle with adapted return type, if it required an unboxing conversion. + */ + private static MethodHandle unboxReturnType(final MethodHandle target, final MethodType newType) { + final MethodType targetType = target.type(); + final Class<?> oldReturnType = targetType.returnType(); + final Class<?> newReturnType = newType.returnType(); + if (TypeUtilities.isWrapperType(oldReturnType)) { + if (newReturnType.isPrimitive()) { + // The contract of setAutoConversionStrategy is such that the difference between newType and targetType + // can only be JLS method invocation conversions. + assert TypeUtilities.isMethodInvocationConvertible(oldReturnType, newReturnType); + return MethodHandles.explicitCastArguments(target, targetType.changeReturnType(newReturnType)); + } + } else if (oldReturnType == void.class && newReturnType == Object.class) { + return MethodHandles.filterReturnValue(target, VOID_TO_OBJECT); + } + return target; + } } diff --git a/src/jdk/nashorn/internal/runtime/linker/BoundCallable.java b/src/jdk/nashorn/internal/runtime/linker/BoundCallable.java new file mode 100644 index 00000000..a0eee0de --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/BoundCallable.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.nashorn.internal.runtime.linker; + +import java.util.Arrays; +import jdk.nashorn.internal.runtime.ScriptRuntime; + +/** + * Represents a Nashorn callable bound to a receiver and optionally arguments. Note that objects of this class + * are just the tuples of a callable and a bound this and arguments, without any behavior. All the behavior is + * defined in the {@code BoundCallableLinker}. + */ +public final class BoundCallable { + private final Object callable; + private final Object boundThis; + private final Object[] boundArgs; + + BoundCallable(final Object callable, final Object boundThis, final Object[] boundArgs) { + this.callable = callable; + this.boundThis = boundThis; + this.boundArgs = isEmptyArray(boundArgs) ? ScriptRuntime.EMPTY_ARRAY : boundArgs.clone(); + } + + private BoundCallable(final BoundCallable original, final Object[] extraBoundArgs) { + this.callable = original.callable; + this.boundThis = original.boundThis; + this.boundArgs = original.concatenateBoundArgs(extraBoundArgs); + } + + Object getCallable() { + return callable; + } + + Object getBoundThis() { + return boundThis; + } + + Object[] getBoundArgs() { + return boundArgs; + } + + BoundCallable bind(final Object[] extraBoundArgs) { + if (isEmptyArray(extraBoundArgs)) { + return this; + } + return new BoundCallable(this, extraBoundArgs); + } + + private Object[] concatenateBoundArgs(final Object[] extraBoundArgs) { + if (boundArgs.length == 0) { + return extraBoundArgs.clone(); + } + final int origBoundArgsLen = boundArgs.length; + final int extraBoundArgsLen = extraBoundArgs.length; + final Object[] newBoundArgs = new Object[origBoundArgsLen + extraBoundArgsLen]; + System.arraycopy(boundArgs, 0, newBoundArgs, 0, origBoundArgsLen); + System.arraycopy(extraBoundArgs, 0, newBoundArgs, origBoundArgsLen, extraBoundArgsLen); + return newBoundArgs; + } + + private static boolean isEmptyArray(final Object[] a) { + return a == null || a.length == 0; + } + + @Override + public String toString() { + final StringBuilder b = new StringBuilder(callable.toString()).append(" on ").append(boundThis); + if (boundArgs.length != 0) { + b.append(" with ").append(Arrays.toString(boundArgs)); + } + return b.toString(); + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/BoundCallableLinker.java b/src/jdk/nashorn/internal/runtime/linker/BoundCallableLinker.java new file mode 100644 index 00000000..d52063bf --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/linker/BoundCallableLinker.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.nashorn.internal.runtime.linker; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; +import jdk.internal.dynalink.CallSiteDescriptor; +import jdk.internal.dynalink.linker.GuardedInvocation; +import jdk.internal.dynalink.linker.LinkRequest; +import jdk.internal.dynalink.linker.LinkerServices; +import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker; +import jdk.internal.dynalink.support.Guards; + +/** + * Links {@link BoundCallable} objects. Passes through to linker services for linking a callable (for either + * "dyn:call" or "dyn:new"), and modifies the returned invocation to deal with the receiver and argument binding. + */ +final class BoundCallableLinker implements TypeBasedGuardingDynamicLinker { + @Override + public boolean canLinkType(final Class<?> type) { + return type == BoundCallable.class; + } + + @Override + public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception { + final Object objBoundCallable = linkRequest.getReceiver(); + if(!(objBoundCallable instanceof BoundCallable)) { + return null; + } + + final CallSiteDescriptor descriptor = linkRequest.getCallSiteDescriptor(); + if (descriptor.getNameTokenCount() < 2 || !"dyn".equals(descriptor.getNameToken(CallSiteDescriptor.SCHEME))) { + return null; + } + final String operation = descriptor.getNameToken(CallSiteDescriptor.OPERATOR); + // We need to distinguish "dyn:new" from "dyn:call" because "dyn:call" sites have parameter list of the form + // "callee, this, args", while "dyn:call" sites have "callee, args" -- they lack the "this" parameter. + final boolean isCall; + if ("new".equals(operation)) { + isCall = false; + } else if ("call".equals(operation)) { + isCall = true; + } else { + // Only dyn:call and dyn:new are supported. + return null; + } + final BoundCallable boundCallable = (BoundCallable)objBoundCallable; + final Object callable = boundCallable.getCallable(); + final Object boundThis = boundCallable.getBoundThis(); + + // We need to ask the linker services for a delegate invocation on the target callable. + + // Replace arguments (boundCallable[, this], args) => (callable[, boundThis], boundArgs, args) when delegating + final Object[] args = linkRequest.getArguments(); + final Object[] boundArgs = boundCallable.getBoundArgs(); + final int argsLen = args.length; + final int boundArgsLen = boundArgs.length; + final Object[] newArgs = new Object[argsLen + boundArgsLen]; + newArgs[0] = callable; + final int firstArgIndex; + if (isCall) { + newArgs[1] = boundThis; + firstArgIndex = 2; + } else { + firstArgIndex = 1; + } + System.arraycopy(boundArgs, 0, newArgs, firstArgIndex, boundArgsLen); + System.arraycopy(args, firstArgIndex, newArgs, firstArgIndex + boundArgsLen, argsLen - firstArgIndex); + + // Use R(T0, T1, T2, ...) => R(callable.class, boundThis.class, boundArg0.class, ..., boundArgn.class, T2, ...) + // call site type when delegating to underlying linker (for dyn:new, there's no this). + final MethodType type = descriptor.getMethodType(); + // Use R(T0, ...) => R(callable.class, ...) + MethodType newMethodType = descriptor.getMethodType().changeParameterType(0, callable.getClass()); + if (isCall) { + // R(callable.class, T1, ...) => R(callable.class, boundThis.class, ...) + newMethodType = newMethodType.changeParameterType(1, boundThis.getClass()); + } + // R(callable.class[, boundThis.class], T2, ...) => R(callable.class[, boundThis.class], boundArg0.class, ..., boundArgn.class, T2, ...) + for(int i = boundArgs.length; i-- > 0;) { + newMethodType = newMethodType.insertParameterTypes(firstArgIndex, boundArgs[i] == null ? Object.class : boundArgs[i].getClass()); + } + final CallSiteDescriptor newDescriptor = descriptor.changeMethodType(newMethodType); + + // Delegate to target's linker + final GuardedInvocation inv = linkerServices.getGuardedInvocation(linkRequest.replaceArguments(newDescriptor, newArgs)); + if(inv == null) { + return null; + } + + // Bind (callable[, boundThis], boundArgs) to the delegate handle + final MethodHandle boundHandle = MethodHandles.insertArguments(inv.getInvocation(), 0, + Arrays.copyOf(newArgs, firstArgIndex + boundArgs.length)); + final Class<?> p0Type = type.parameterType(0); + final MethodHandle droppingHandle; + if (isCall) { + // Ignore incoming boundCallable and this + droppingHandle = MethodHandles.dropArguments(boundHandle, 0, p0Type, type.parameterType(1)); + } else { + // Ignore incoming boundCallable + droppingHandle = MethodHandles.dropArguments(boundHandle, 0, p0Type); + } + // Identity guard on boundCallable object + final MethodHandle newGuard = Guards.getIdentityGuard(boundCallable); + return inv.replaceMethods(droppingHandle, newGuard.asType(newGuard.type().changeParameterType(0, p0Type))); + } +} diff --git a/src/jdk/nashorn/internal/runtime/linker/BoundDynamicMethodLinker.java b/src/jdk/nashorn/internal/runtime/linker/BoundDynamicMethodLinker.java deleted file mode 100644 index 67e29835..00000000 --- a/src/jdk/nashorn/internal/runtime/linker/BoundDynamicMethodLinker.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.nashorn.internal.runtime.linker; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import jdk.internal.dynalink.CallSiteDescriptor; -import jdk.internal.dynalink.beans.BeansLinker; -import jdk.internal.dynalink.linker.GuardedInvocation; -import jdk.internal.dynalink.linker.LinkRequest; -import jdk.internal.dynalink.linker.LinkerServices; -import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker; -import jdk.internal.dynalink.support.Guards; - -/** - * Links {@code BoundDynamicMethod} objects. Passes through to Dynalink's BeansLinker for linking a dynamic method - * (they only respond to "dyn:call"), and modifies the returned invocation to deal with the receiver binding. - */ -final class BoundDynamicMethodLinker implements TypeBasedGuardingDynamicLinker { - @Override - public boolean canLinkType(final Class<?> type) { - return type == BoundDynamicMethod.class; - } - - @Override - public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception { - final Object objBoundDynamicMethod = linkRequest.getReceiver(); - if(!(objBoundDynamicMethod instanceof BoundDynamicMethod)) { - return null; - } - - final BoundDynamicMethod boundDynamicMethod = (BoundDynamicMethod)objBoundDynamicMethod; - final Object dynamicMethod = boundDynamicMethod.getDynamicMethod(); - final Object boundThis = boundDynamicMethod.getBoundThis(); - - // Replace arguments (boundDynamicMethod, this, ...) => (dynamicMethod, boundThis, ...) when delegating to - // BeansLinker - final Object[] args = linkRequest.getArguments(); - args[0] = dynamicMethod; - args[1] = boundThis; - - // Use R(T0, T1, ...) => R(dynamicMethod.class, boundThis.class, ...) call site type when delegating to - // BeansLinker. - final CallSiteDescriptor descriptor = linkRequest.getCallSiteDescriptor(); - final MethodType type = descriptor.getMethodType(); - final Class<?> dynamicMethodClass = dynamicMethod.getClass(); - final CallSiteDescriptor newDescriptor = descriptor.changeMethodType( - type.changeParameterType(0, dynamicMethodClass).changeParameterType(1, boundThis.getClass())); - - // Delegate to BeansLinker - final GuardedInvocation inv = NashornBeansLinker.getGuardedInvocation(BeansLinker.getLinkerForClass(dynamicMethodClass), - linkRequest.replaceArguments(newDescriptor, args), linkerServices); - if(inv == null) { - return null; - } - - // Bind (dynamicMethod, boundThis) to the handle - final MethodHandle boundHandle = MethodHandles.insertArguments(inv.getInvocation(), 0, dynamicMethod, boundThis); - final Class<?> p0Type = type.parameterType(0); - // Ignore incoming (boundDynamicMethod, this) - final MethodHandle droppingHandle = MethodHandles.dropArguments(boundHandle, 0, p0Type, type.parameterType(1)); - // Identity guard on boundDynamicMethod object - final MethodHandle newGuard = Guards.getIdentityGuard(boundDynamicMethod); - - return inv.replaceMethods(droppingHandle, newGuard.asType(newGuard.type().changeParameterType(0, p0Type))); - } -} diff --git a/src/jdk/nashorn/internal/runtime/linker/BrowserJSObjectLinker.java b/src/jdk/nashorn/internal/runtime/linker/BrowserJSObjectLinker.java index e8cfa4ed..d2f23e82 100644 --- a/src/jdk/nashorn/internal/runtime/linker/BrowserJSObjectLinker.java +++ b/src/jdk/nashorn/internal/runtime/linker/BrowserJSObjectLinker.java @@ -25,8 +25,11 @@ package jdk.nashorn.internal.runtime.linker; -import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.*; - +import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_GETMEMBER; +import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_GETSLOT; +import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_SETMEMBER; +import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_SETSLOT; +import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_CALL; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import jdk.internal.dynalink.CallSiteDescriptor; @@ -114,15 +117,15 @@ final class BrowserJSObjectLinker implements TypeBasedGuardingDynamicLinker { case "getMethod": if (c > 2) { return findGetMethod(desc); - } else { - // For indexed get, we want GuardedInvocation from beans linker and pass it. - // BrowserJSObjectLinker.get uses this fallback getter for explicit signature method access. - final GuardedInvocation beanInv = nashornBeansLinker.getGuardedInvocation(request, linkerServices); - return findGetIndexMethod(beanInv); } + // For indexed get, we want GuardedInvocation from beans linker and pass it. + // BrowserJSObjectLinker.get uses this fallback getter for explicit signature method access. + return findGetIndexMethod(nashornBeansLinker.getGuardedInvocation(request, linkerServices)); case "setProp": case "setElem": return c > 2 ? findSetMethod(desc) : findSetIndexMethod(); + case "call": + return findCallMethod(desc); default: return null; } @@ -148,6 +151,11 @@ final class BrowserJSObjectLinker implements TypeBasedGuardingDynamicLinker { return new GuardedInvocation(JSOBJECTLINKER_PUT, IS_JSOBJECT_GUARD); } + private static GuardedInvocation findCallMethod(final CallSiteDescriptor desc) { + final MethodHandle call = MH.insertArguments(JSOBJECT_CALL, 1, "call"); + return new GuardedInvocation(MH.asCollector(call, Object[].class, desc.getMethodType().parameterCount() - 1), IS_JSOBJECT_GUARD); + } + @SuppressWarnings("unused") private static boolean isJSObject(final Object self) { return jsObjectClass.isInstance(self); @@ -166,9 +174,8 @@ final class BrowserJSObjectLinker implements TypeBasedGuardingDynamicLinker { final String name = (String)key; if (name.indexOf('(') != -1) { return fallback.invokeExact(jsobj, key); - } else { - return JSOBJECT_GETMEMBER.invokeExact(jsobj, (String)key); } + return JSOBJECT_GETMEMBER.invokeExact(jsobj, (String)key); } return null; } @@ -208,6 +215,7 @@ final class BrowserJSObjectLinker implements TypeBasedGuardingDynamicLinker { static final MethodHandle JSOBJECT_GETSLOT = findJSObjectMH_V("getSlot", Object.class, int.class).asType(MH.type(Object.class, Object.class, int.class)); static final MethodHandle JSOBJECT_SETMEMBER = findJSObjectMH_V("setMember", Void.TYPE, String.class, Object.class).asType(MH.type(Void.TYPE, Object.class, String.class, Object.class)); static final MethodHandle JSOBJECT_SETSLOT = findJSObjectMH_V("setSlot", Void.TYPE, int.class, Object.class).asType(MH.type(Void.TYPE, Object.class, int.class, Object.class)); + static final MethodHandle JSOBJECT_CALL = findJSObjectMH_V("call", Object.class, String.class, Object[].class).asType(MH.type(Object.class, Object.class, String.class, Object[].class)); private static MethodHandle findJSObjectMH_V(final String name, final Class<?> rtype, final Class<?>... types) { checkJSObjectClass(); diff --git a/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java b/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java index 67c8e4bb..aaf5a2c3 100644 --- a/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java +++ b/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java @@ -120,12 +120,10 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTy case "getMethod": if (c > 2) { return findGetMethod(desc); - } else { - // For indexed get, we want get GuardedInvocation beans linker and pass it. - // JSObjectLinker.get uses this fallback getter for explicit signature method access. - final GuardedInvocation beanInv = nashornBeansLinker.getGuardedInvocation(request, linkerServices); - return findGetIndexMethod(beanInv); } + // For indexed get, we want get GuardedInvocation beans linker and pass it. + // JSObjectLinker.get uses this fallback getter for explicit signature method access. + return findGetIndexMethod(nashornBeansLinker.getGuardedInvocation(request, linkerServices)); case "setProp": case "setElem": return c > 2 ? findSetMethod(desc) : findSetIndexMethod(); @@ -192,9 +190,8 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTy // get with method name and signature. delegate it to beans linker! if (name.indexOf('(') != -1) { return fallback.invokeExact(jsobj, key); - } else { - return ((JSObject)jsobj).getMember(name); } + return ((JSObject)jsobj).getMember(name); } return null; } diff --git a/src/jdk/nashorn/internal/runtime/linker/JavaSuperAdapterLinker.java b/src/jdk/nashorn/internal/runtime/linker/JavaSuperAdapterLinker.java index 5fc93cb4..9aeefd72 100644 --- a/src/jdk/nashorn/internal/runtime/linker/JavaSuperAdapterLinker.java +++ b/src/jdk/nashorn/internal/runtime/linker/JavaSuperAdapterLinker.java @@ -165,7 +165,7 @@ final class JavaSuperAdapterLinker implements TypeBasedGuardingDynamicLinker { */ @SuppressWarnings("unused") private static Object bindDynamicMethod(final Object dynamicMethod, final Object boundThis) { - return dynamicMethod == null ? ScriptRuntime.UNDEFINED : Bootstrap.bindDynamicMethod(dynamicMethod, boundThis); + return dynamicMethod == null ? ScriptRuntime.UNDEFINED : Bootstrap.bindCallable(dynamicMethod, boundThis, null); } /** diff --git a/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java b/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java index 25ba619b..da12af2d 100644 --- a/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java +++ b/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java @@ -25,20 +25,29 @@ package jdk.nashorn.internal.runtime.linker; +import static jdk.nashorn.internal.lookup.Lookup.MH; +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; + import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.beans.BeansLinker; import jdk.internal.dynalink.linker.ConversionComparator.Comparison; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.GuardingDynamicLinker; import jdk.internal.dynalink.linker.LinkRequest; import jdk.internal.dynalink.linker.LinkerServices; +import jdk.internal.dynalink.support.Guards; import jdk.internal.dynalink.support.Lookup; import jdk.nashorn.api.scripting.ScriptUtils; import jdk.nashorn.internal.objects.NativeArray; import jdk.nashorn.internal.runtime.ConsString; +import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.options.Options; /** @@ -53,15 +62,64 @@ public class NashornBeansLinker implements GuardingDynamicLinker { // Object type arguments of Java method calls, field set and array set. private static final boolean MIRROR_ALWAYS = Options.getBooleanProperty("nashorn.mirror.always", true); - private static final MethodHandle EXPORT_ARGUMENT = new Lookup(MethodHandles.lookup()).findOwnStatic("exportArgument", Object.class, Object.class); - private static final MethodHandle EXPORT_NATIVE_ARRAY = new Lookup(MethodHandles.lookup()).findOwnStatic("exportNativeArray", Object.class, NativeArray.class); - private static final MethodHandle EXPORT_SCRIPT_OBJECT = new Lookup(MethodHandles.lookup()).findOwnStatic("exportScriptObject", Object.class, ScriptObject.class); - private static final MethodHandle IMPORT_RESULT = new Lookup(MethodHandles.lookup()).findOwnStatic("importResult", Object.class, Object.class); + private static final MethodHandle EXPORT_ARGUMENT; + private static final MethodHandle EXPORT_NATIVE_ARRAY; + private static final MethodHandle EXPORT_SCRIPT_OBJECT; + private static final MethodHandle IMPORT_RESULT; + private static final MethodHandle FILTER_CONSSTRING; + + static { + final Lookup lookup = new Lookup(MethodHandles.lookup()); + EXPORT_ARGUMENT = lookup.findOwnStatic("exportArgument", Object.class, Object.class); + EXPORT_NATIVE_ARRAY = lookup.findOwnStatic("exportNativeArray", Object.class, NativeArray.class); + EXPORT_SCRIPT_OBJECT = lookup.findOwnStatic("exportScriptObject", Object.class, ScriptObject.class); + IMPORT_RESULT = lookup.findOwnStatic("importResult", Object.class, Object.class); + FILTER_CONSSTRING = lookup.findOwnStatic("consStringFilter", Object.class, Object.class); + } + + // cache of @FunctionalInterface method of implementor classes + private static final ClassValue<Method> FUNCTIONAL_IFACE_METHOD = new ClassValue<Method>() { + @Override + protected Method computeValue(final Class<?> type) { + return findFunctionalInterfaceMethod(type); + } + }; private final BeansLinker beansLinker = new BeansLinker(); @Override public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception { + final Object self = linkRequest.getReceiver(); + final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor(); + if (self instanceof ConsString) { + // In order to treat ConsString like a java.lang.String we need a link request with a string receiver. + final Object[] arguments = linkRequest.getArguments(); + arguments[0] = ""; + final LinkRequest forgedLinkRequest = linkRequest.replaceArguments(desc, arguments); + final GuardedInvocation invocation = getGuardedInvocation(beansLinker, forgedLinkRequest, linkerServices); + // If an invocation is found we add a filter that makes it work for both Strings and ConsStrings. + return invocation == null ? null : invocation.filterArguments(0, FILTER_CONSSTRING); + } + + if (self != null && "call".equals(desc.getNameToken(CallSiteDescriptor.OPERATOR))) { + // Support dyn:call on any object that supports some @FunctionalInterface + // annotated interface. This way Java method, constructor references or + // implementations of java.util.function.* interfaces can be called as though + // those are script functions. + final Method m = getFunctionalInterfaceMethod(self.getClass()); + if (m != null) { + final MethodType callType = desc.getMethodType(); + // 'callee' and 'thiz' passed from script + actual arguments + if (callType.parameterCount() != m.getParameterCount() + 2) { + throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self)); + } + return new GuardedInvocation( + // drop 'thiz' passed from the script. + MH.dropArguments(desc.getLookup().unreflect(m), 1, callType.parameterType(1)), + Guards.getInstanceOfGuard(m.getDeclaringClass())).asTypeSafeReturn( + new NashornBeansLinkerServices(linkerServices), callType); + } + } return getGuardedInvocation(beansLinker, linkRequest, linkerServices); } @@ -113,6 +171,43 @@ public class NashornBeansLinker implements GuardingDynamicLinker { return ScriptUtils.unwrap(arg); } + @SuppressWarnings("unused") + private static Object consStringFilter(final Object arg) { + return arg instanceof ConsString ? arg.toString() : arg; + } + + private static Method findFunctionalInterfaceMethod(final Class<?> clazz) { + if (clazz == null) { + return null; + } + + for (final Class<?> iface : clazz.getInterfaces()) { + // check accessiblity up-front + if (! Context.isAccessibleClass(iface)) { + continue; + } + + // check for @FunctionalInterface + if (iface.isAnnotationPresent(FunctionalInterface.class)) { + // return the first abstract method + for (final Method m : iface.getMethods()) { + if (Modifier.isAbstract(m.getModifiers())) { + return m; + } + } + } + } + + // did not find here, try super class + return findFunctionalInterfaceMethod(clazz.getSuperclass()); + } + + // Returns @FunctionalInterface annotated interface's single abstract + // method. If not found, returns null. + static Method getFunctionalInterfaceMethod(final Class<?> clazz) { + return FUNCTIONAL_IFACE_METHOD.get(clazz); + } + private static class NashornBeansLinkerServices implements LinkerServices { private final LinkerServices linkerServices; diff --git a/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java b/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java index 4ed6e3a2..2cbbf065 100644 --- a/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java +++ b/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java @@ -30,9 +30,6 @@ import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodType; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import jdk.internal.dynalink.CallSiteDescriptor; @@ -45,7 +42,6 @@ import jdk.internal.dynalink.linker.LinkRequest; import jdk.internal.dynalink.linker.LinkerServices; import jdk.internal.dynalink.support.Guards; import jdk.nashorn.internal.codegen.types.Type; -import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; @@ -95,22 +91,6 @@ final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeCo } throw typeError("not.a.function", ScriptRuntime.safeToString(self)); case "call": - // Support dyn:call on any object that supports some @FunctionalInterface - // annotated interface. This way Java method, constructor references or - // implementations of java.util.function.* interfaces can be called as though - // those are script functions. - final Method m = getFunctionalInterfaceMethod(self.getClass()); - if (m != null) { - final MethodType callType = desc.getMethodType(); - // 'callee' and 'thiz' passed from script + actual arguments - if (callType.parameterCount() != m.getParameterCount() + 2) { - throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self)); - } - return Bootstrap.asTypeSafeReturn(new GuardedInvocation( - // drop 'thiz' passed from the script. - MH.dropArguments(desc.getLookup().unreflect(m), 1, callType.parameterType(1)), - Guards.getInstanceOfGuard(m.getDeclaringClass())), linkerServices, desc); - } if(BeansLinker.isDynamicConstructor(self)) { throw typeError("constructor.requires.new", ScriptRuntime.safeToString(self)); } @@ -218,44 +198,4 @@ final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeCo } return ScriptRuntime.safeToString(linkRequest.getArguments()[1]); } - - // cache of @FunctionalInterface method of implementor classes - private static final ClassValue<Method> FUNCTIONAL_IFACE_METHOD = new ClassValue<Method>() { - @Override - protected Method computeValue(final Class<?> type) { - return findFunctionalInterfaceMethod(type); - } - - private Method findFunctionalInterfaceMethod(final Class<?> clazz) { - if (clazz == null) { - return null; - } - - for (final Class<?> iface : clazz.getInterfaces()) { - // check accessiblity up-front - if (! Context.isAccessibleClass(iface)) { - continue; - } - - // check for @FunctionalInterface - if (iface.isAnnotationPresent(FunctionalInterface.class)) { - // return the first abstract method - for (final Method m : iface.getMethods()) { - if (Modifier.isAbstract(m.getModifiers())) { - return m; - } - } - } - } - - // did not find here, try super class - return findFunctionalInterfaceMethod(clazz.getSuperclass()); - } - }; - - // Returns @FunctionalInterface annotated interface's single abstract - // method. If not found, returns null. - static Method getFunctionalInterfaceMethod(final Class<?> clazz) { - return FUNCTIONAL_IFACE_METHOD.get(clazz); - } } diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/Analyser.java b/src/jdk/nashorn/internal/runtime/regexp/joni/Analyser.java index 6a9ebc1a..64d90c03 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/Analyser.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/Analyser.java @@ -27,7 +27,6 @@ import static jdk.nashorn.internal.runtime.regexp.joni.Option.isIgnoreCase; import static jdk.nashorn.internal.runtime.regexp.joni.Option.isMultiline; import static jdk.nashorn.internal.runtime.regexp.joni.ast.ConsAltNode.newAltNode; import static jdk.nashorn.internal.runtime.regexp.joni.ast.QuantifierNode.isRepeatInfinite; - import java.util.HashSet; import jdk.nashorn.internal.runtime.regexp.joni.ast.AnchorNode; import jdk.nashorn.internal.runtime.regexp.joni.ast.BackRefNode; @@ -53,6 +52,7 @@ final class Analyser extends Parser { super(env, chars, p, end); } + @SuppressWarnings("unused") protected final void compile() { if (Config.DEBUG) { Config.log.println(new String(chars, getBegin(), getEnd())); @@ -76,7 +76,9 @@ final class Analyser extends Parser { root = setupTree(root, 0); if (Config.DEBUG_PARSE_TREE) { - if (Config.DEBUG_PARSE_TREE_RAW) Config.log.println("<TREE>"); + if (Config.DEBUG_PARSE_TREE_RAW) { + Config.log.println("<TREE>"); + } root.verifyTree(new HashSet<Node>(), env.reg.warnings); Config.log.println(root + "\n"); } @@ -94,7 +96,9 @@ final class Analyser extends Parser { regex.clearOptimizeInfo(); - if (!Config.DONT_OPTIMIZE) setOptimizedInfoFromTree(root); + if (!Config.DONT_OPTIMIZE) { + setOptimizedInfoFromTree(root); + } env.memNodes = null; @@ -110,7 +114,9 @@ final class Analyser extends Parser { if (Config.DEBUG_COMPILE) { Config.log.println("stack used: " + regex.stackNeeded); - if (Config.USE_STRING_TEMPLATES) Config.log.print("templates: " + regex.templateNum + "\n"); + if (Config.USE_STRING_TEMPLATES) { + Config.log.print("templates: " + regex.templateNum + "\n"); + } Config.log.println(new ByteCodePrinter(regex).byteCodeListToString()); } // DEBUG_COMPILE @@ -136,7 +142,9 @@ final class Analyser extends Parser { ConsAltNode can = (ConsAltNode)node; do { final int v = quantifiersMemoryInfo(can.car); - if (v > info) info = v; + if (v > info) { + info = v; + } } while ((can = can.cdr) != null); break; @@ -182,7 +190,9 @@ final class Analyser extends Parser { switch (node.getType()) { case NodeType.BREF: final BackRefNode br = (BackRefNode)node; - if (br.isRecursion()) break; + if (br.isRecursion()) { + break; + } if (br.backRef > env.numMem) { throw new ValueException(ERR_INVALID_BACKREF); @@ -249,6 +259,9 @@ final class Analyser extends Parser { case EncloseType.STOP_BACKTRACK: min = getMinMatchLength(en.target); break; + + default: + break; } // inner switch break; @@ -276,7 +289,9 @@ final class Analyser extends Parser { ConsAltNode an = (ConsAltNode)node; do { final int tmax = getMaxMatchLength(an.car); - if (max < tmax) max = tmax; + if (max < tmax) { + max = tmax; + } } while ((an = an.cdr) != null); break; @@ -304,7 +319,9 @@ final class Analyser extends Parser { throw new ValueException(ERR_INVALID_BACKREF); } final int tmax = getMaxMatchLength(env.memNodes[br.backRef]); - if (max < tmax) max = tmax; + if (max < tmax) { + max = tmax; + } break; case NodeType.QTFR: @@ -338,6 +355,9 @@ final class Analyser extends Parser { case EncloseType.STOP_BACKTRACK: max = getMaxMatchLength(en.target); break; + + default: + break; } // inner switch break; @@ -355,8 +375,8 @@ final class Analyser extends Parser { return getCharLengthTree(node, 0); } - private int getCharLengthTree(final Node node, int level) { - level++; + private int getCharLengthTree(final Node node, final int levelp) { + final int level = levelp + 1; int len = 0; returnCode = 0; @@ -366,7 +386,9 @@ final class Analyser extends Parser { ConsAltNode ln = (ConsAltNode)node; do { final int tlen = getCharLengthTree(ln.car, level); - if (returnCode == 0) len = MinMaxLen.distanceAdd(len, tlen); + if (returnCode == 0) { + len = MinMaxLen.distanceAdd(len, tlen); + } } while (returnCode == 0 && (ln = ln.cdr) != null); break; @@ -378,7 +400,9 @@ final class Analyser extends Parser { while (returnCode == 0 && (an = an.cdr) != null) { final int tlen2 = getCharLengthTree(an.car, level); if (returnCode == 0) { - if (tlen != tlen2) varLen = true; + if (tlen != tlen2) { + varLen = true; + } } } @@ -404,7 +428,9 @@ final class Analyser extends Parser { final QuantifierNode qn = (QuantifierNode)node; if (qn.lower == qn.upper) { tlen = getCharLengthTree(qn.target, level); - if (returnCode == 0) len = MinMaxLen.distanceMultiply(tlen, qn.lower); + if (returnCode == 0) { + len = MinMaxLen.distanceMultiply(tlen, qn.lower); + } } else { returnCode = GET_CHAR_LEN_VARLEN; } @@ -435,6 +461,9 @@ final class Analyser extends Parser { case EncloseType.STOP_BACKTRACK: len = getCharLengthTree(en.target, level); break; + + default: + break; } // inner switch break; @@ -448,7 +477,9 @@ final class Analyser extends Parser { } /* x is not included y ==> 1 : 0 */ - private boolean isNotIncluded(Node x, Node y) { + private static boolean isNotIncluded(final Node xn, final Node yn) { + Node x = xn; + Node y = yn; Node tmp; // !retry:! @@ -492,10 +523,14 @@ final class Analyser extends Parser { boolean v = xc.bs.at(i); if ((v && !xc.isNot()) || (!v && xc.isNot())) { v = yc.bs.at(i); - if ((v && !yc.isNot()) || (!v && yc.isNot())) return false; + if ((v && !yc.isNot()) || (!v && yc.isNot())) { + return false; + } } } - if ((xc.mbuf == null && !xc.isNot()) || yc.mbuf == null && !yc.isNot()) return true; + if ((xc.mbuf == null && !xc.isNot()) || yc.mbuf == null && !yc.isNot()) { + return true; + } return false; // break; not reached @@ -514,7 +549,9 @@ final class Analyser extends Parser { case NodeType.STR: final StringNode xs = (StringNode)x; - if (xs.length() == 0) break; + if (xs.length() == 0) { + break; + } switch (yType) { @@ -526,13 +563,16 @@ final class Analyser extends Parser { case NodeType.STR: final StringNode ys = (StringNode)y; int len = xs.length(); - if (len > ys.length()) len = ys.length(); + if (len > ys.length()) { + len = ys.length(); + } if (xs.isAmbig() || ys.isAmbig()) { /* tiny version */ return false; - } else { - for (int i=0, p=ys.p, q=xs.p; i<len; i++, p++, q++) { - if (ys.chars[p] != xs.chars[q]) return true; + } + for (int i=0, pt=ys.p, q=xs.p; i<len; i++, pt++, q++) { + if (ys.chars[pt] != xs.chars[q]) { + return true; } } break; @@ -542,6 +582,8 @@ final class Analyser extends Parser { } // inner switch break; // case NodeType.STR + default: + break; } // switch @@ -561,7 +603,9 @@ final class Analyser extends Parser { case NodeType.CTYPE: case NodeType.CCLASS: - if (!exact) n = node; + if (!exact) { + n = node; + } break; case NodeType.LIST: @@ -570,7 +614,10 @@ final class Analyser extends Parser { case NodeType.STR: final StringNode sn = (StringNode)node; - if (sn.end <= sn.p) break; // ??? + if (sn.end <= sn.p) + { + break; // ??? + } if (exact && !sn.isRaw() && isIgnoreCase(regex.options)){ // nothing @@ -605,12 +652,17 @@ final class Analyser extends Parser { case EncloseType.STOP_BACKTRACK: n = getHeadValueNode(en.target, exact); break; + + default: + break; } // inner switch break; case NodeType.ANCHOR: final AnchorNode an = (AnchorNode)node; - if (an.type == AnchorType.PREC_READ) n = getHeadValueNode(an.target, exact); + if (an.type == AnchorType.PREC_READ) { + n = getHeadValueNode(an.target, exact); + } break; default: @@ -622,7 +674,9 @@ final class Analyser extends Parser { // true: invalid private boolean checkTypeTree(final Node node, final int typeMask, final int encloseMask, final int anchorMask) { - if ((node.getType2Bit() & typeMask) == 0) return true; + if ((node.getType2Bit() & typeMask) == 0) { + return true; + } boolean invalid = false; @@ -641,15 +695,21 @@ final class Analyser extends Parser { case NodeType.ENCLOSE: final EncloseNode en = (EncloseNode)node; - if ((en.type & encloseMask) == 0) return true; + if ((en.type & encloseMask) == 0) { + return true; + } invalid = checkTypeTree(en.target, typeMask, encloseMask, anchorMask); break; case NodeType.ANCHOR: final AnchorNode an = (AnchorNode)node; - if ((an.type & anchorMask) == 0) return true; + if ((an.type & anchorMask) == 0) { + return true; + } - if (an.target != null) invalid = checkTypeTree(an.target, typeMask, encloseMask, anchorMask); + if (an.target != null) { + invalid = checkTypeTree(an.target, typeMask, encloseMask, anchorMask); + } break; default: @@ -664,7 +724,8 @@ final class Analyser extends Parser { (?<=A|B) ==> (?<=A)|(?<=B) (?<!A|B) ==> (?<!A)(?<!B) */ - private Node divideLookBehindAlternatives(Node node) { + private Node divideLookBehindAlternatives(final Node nodep) { + Node node = nodep; final AnchorNode an = (AnchorNode)node; final int anchorType = an.type; Node head = an.target; @@ -699,7 +760,7 @@ final class Analyser extends Parser { private Node setupLookBehind(final Node node) { final AnchorNode an = (AnchorNode)node; final int len = getCharLengthTree(an.target); - switch(returnCode) { + switch (returnCode) { case 0: an.charLength = len; break; @@ -708,14 +769,17 @@ final class Analyser extends Parser { case GET_CHAR_LEN_TOP_ALT_VARLEN: if (syntax.differentLengthAltLookBehind()) { return divideLookBehindAlternatives(node); - } else { - throw new SyntaxException(ERR_INVALID_LOOK_BEHIND_PATTERN); } + throw new SyntaxException(ERR_INVALID_LOOK_BEHIND_PATTERN); + default: + break; } return node; } - private void nextSetup(Node node, final Node nextNode) { + private void nextSetup(final Node nodep, final Node nextNode) { + Node node = nodep; + // retry: retry: while(true) { @@ -762,7 +826,7 @@ final class Analyser extends Parser { } private void updateStringNodeCaseFoldMultiByte(final StringNode sn) { - final char[] chars = sn.chars; + final char[] ch = sn.chars; final int end = sn.end; value = sn.p; int sp = 0; @@ -770,15 +834,15 @@ final class Analyser extends Parser { while (value < end) { final int ovalue = value; - buf = EncodingHelper.toLowerCase(chars[value++]); + buf = EncodingHelper.toLowerCase(ch[value++]); - if (chars[ovalue] != buf) { + if (ch[ovalue] != buf) { char[] sbuf = new char[sn.length() << 1]; - System.arraycopy(chars, sn.p, sbuf, 0, ovalue - sn.p); + System.arraycopy(ch, sn.p, sbuf, 0, ovalue - sn.p); value = ovalue; while (value < end) { - buf = EncodingHelper.toLowerCase(chars[value++]); + buf = EncodingHelper.toLowerCase(ch[value++]); if (sp >= sbuf.length) { final char[]tmp = new char[sbuf.length << 1]; System.arraycopy(sbuf, 0, tmp, 0, sbuf.length); @@ -798,8 +862,8 @@ final class Analyser extends Parser { updateStringNodeCaseFoldMultiByte(sn); } - private Node expandCaseFoldMakeRemString(final char[] chars, final int p, final int end) { - final StringNode node = new StringNode(chars, p, end); + private Node expandCaseFoldMakeRemString(final char[] ch, final int pp, final int end) { + final StringNode node = new StringNode(ch, pp, end); updateStringNodeCaseFold(node); node.setAmbig(); @@ -807,7 +871,7 @@ final class Analyser extends Parser { return node; } - private boolean expandCaseFoldStringAlt(final int itemNum, final char[] items, + private static boolean expandCaseFoldStringAlt(final int itemNum, final char[] items, final char[] chars, final int p, final int slen, final int end, final ObjPtr<Node> node) { ConsAltNode altNode; @@ -833,59 +897,68 @@ final class Analyser extends Parser { private Node expandCaseFoldString(final Node node) { final StringNode sn = (StringNode)node; - if (sn.isAmbig() || sn.length() <= 0) return node; + if (sn.isAmbig() || sn.length() <= 0) { + return node; + } - final char[] chars = sn.chars; - int p = sn.p; + final char[] chars1 = sn.chars; + int pt = sn.p; final int end = sn.end; int altNum = 1; - ConsAltNode topRoot = null, root = null; + ConsAltNode topRoot = null, r = null; + @SuppressWarnings("unused") final ObjPtr<Node> prevNode = new ObjPtr<Node>(); StringNode stringNode = null; - while (p < end) { - final char[] items = EncodingHelper.caseFoldCodesByString(regex.caseFoldFlag, chars[p]); + while (pt < end) { + final char[] items = EncodingHelper.caseFoldCodesByString(regex.caseFoldFlag, chars1[pt]); if (items.length == 0) { if (stringNode == null) { - if (root == null && prevNode.p != null) { - topRoot = root = ConsAltNode.listAdd(null, prevNode.p); + if (r == null && prevNode.p != null) { + topRoot = r = ConsAltNode.listAdd(null, prevNode.p); } prevNode.p = stringNode = new StringNode(); // onig_node_new_str(NULL, NULL); - if (root != null) ConsAltNode.listAdd(root, stringNode); + if (r != null) { + ConsAltNode.listAdd(r, stringNode); + } } - stringNode.cat(chars, p, p + 1); + stringNode.cat(chars1, pt, pt + 1); } else { altNum *= (items.length + 1); - if (altNum > THRESHOLD_CASE_FOLD_ALT_FOR_EXPANSION) break; + if (altNum > THRESHOLD_CASE_FOLD_ALT_FOR_EXPANSION) { + break; + } - if (root == null && prevNode.p != null) { - topRoot = root = ConsAltNode.listAdd(null, prevNode.p); + if (r == null && prevNode.p != null) { + topRoot = r = ConsAltNode.listAdd(null, prevNode.p); } - expandCaseFoldStringAlt(items.length, items, chars, p, 1, end, prevNode); - if (root != null) ConsAltNode.listAdd(root, prevNode.p); + expandCaseFoldStringAlt(items.length, items, chars1, pt, 1, end, prevNode); + if (r != null) { + ConsAltNode.listAdd(r, prevNode.p); + } stringNode = null; } - p++; + pt++; } - if (p < end) { - final Node srem = expandCaseFoldMakeRemString(chars, p, end); + if (pt < end) { + final Node srem = expandCaseFoldMakeRemString(chars1, pt, end); - if (prevNode.p != null && root == null) { - topRoot = root = ConsAltNode.listAdd(null, prevNode.p); + if (prevNode.p != null && r == null) { + topRoot = r = ConsAltNode.listAdd(null, prevNode.p); } - if (root == null) { + if (r == null) { prevNode.p = srem; } else { - ConsAltNode.listAdd(root, srem); + ConsAltNode.listAdd(r, srem); } } /* ending */ @@ -909,7 +982,10 @@ final class Analyser extends Parser { 5. find invalid patterns in look-behind. 6. expand repeated string. */ - protected final Node setupTree(Node node, int state) { + protected final Node setupTree(final Node nodep, final int statep) { + Node node = nodep; + int state = statep; + restart: while (true) { switch (node.getType()) { case NodeType.LIST: @@ -958,7 +1034,9 @@ final class Analyser extends Parser { final QuantifierNode qn = (QuantifierNode)node; Node target = qn.target; - if ((state & IN_REPEAT) != 0) qn.setInRepeat(); + if ((state & IN_REPEAT) != 0) { + qn.setInRepeat(); + } if (isRepeatInfinite(qn.upper) || qn.lower >= 1) { final int d = getMinMatchLength(target); @@ -966,14 +1044,18 @@ final class Analyser extends Parser { qn.targetEmptyInfo = TargetInfo.IS_EMPTY; if (Config.USE_MONOMANIAC_CHECK_CAPTURES_IN_ENDLESS_REPEAT) { final int info = quantifiersMemoryInfo(target); - if (info > 0) qn.targetEmptyInfo = info; + if (info > 0) { + qn.targetEmptyInfo = info; + } } // USE_INFINITE_REPEAT_MONOMANIAC_MEM_STATUS_CHECK // strange stuff here (turned off) } } state |= IN_REPEAT; - if (qn.lower != qn.upper) state |= IN_VAR_REPEAT; + if (qn.lower != qn.upper) { + state |= IN_VAR_REPEAT; + } target = setupTree(target, state); @@ -1035,11 +1117,16 @@ final class Analyser extends Parser { final QuantifierNode tqn = (QuantifierNode)en.target; if (isRepeatInfinite(tqn.upper) && tqn.lower <= 1 && tqn.greedy) { /* (?>a*), a*+ etc... */ - if (tqn.target.isSimple()) en.setStopBtSimpleRepeat(); + if (tqn.target.isSimple()) { + en.setStopBtSimpleRepeat(); + } } } break; + default: + break; + } // inner switch break; @@ -1059,7 +1146,9 @@ final class Analyser extends Parser { throw new SyntaxException(ERR_INVALID_LOOK_BEHIND_PATTERN); } node = setupLookBehind(node); - if (node.getType() != NodeType.ANCHOR) continue restart; + if (node.getType() != NodeType.ANCHOR) { + continue restart; + } setupTree(((AnchorNode)node).target, state); break; @@ -1068,12 +1157,19 @@ final class Analyser extends Parser { throw new SyntaxException(ERR_INVALID_LOOK_BEHIND_PATTERN); } node = setupLookBehind(node); - if (node.getType() != NodeType.ANCHOR) continue restart; + if (node.getType() != NodeType.ANCHOR) { + continue restart; + } setupTree(((AnchorNode)node).target, (state | IN_NOT)); break; + default: + break; + } // inner switch break; + default: + break; } // switch return node; } // restart: while @@ -1191,7 +1287,9 @@ final class Analyser extends Parser { opt.expr.copy(nopt.exm); } opt.expr.reachEnd = false; - if (nopt.map.value > 0) opt.map.copy(nopt.map); + if (nopt.map.value > 0) { + opt.map.copy(nopt.map); + } break; case AnchorType.PREC_READ_NOT: @@ -1199,6 +1297,9 @@ final class Analyser extends Parser { case AnchorType.LOOK_BEHIND_NOT: break; + default: + break; + } // inner switch break; } @@ -1282,8 +1383,12 @@ final class Analyser extends Parser { if (++en.optCount > MAX_NODE_OPT_INFO_REF_COUNT) { int min = 0; int max = MinMaxLen.INFINITE_DISTANCE; - if (en.isMinFixed()) min = en.minLength; - if (en.isMaxFixed()) max = en.maxLength; + if (en.isMinFixed()) { + min = en.minLength; + } + if (en.isMaxFixed()) { + max = en.maxLength; + } opt.length.set(min, max); } else { // USE_SUBEXP_CALL optimizeNodeLeft(en.target, opt, oenv); @@ -1298,6 +1403,9 @@ final class Analyser extends Parser { case EncloseType.STOP_BACKTRACK: optimizeNodeLeft(en.target, opt, oenv); break; + + default: + break; } // inner switch break; } @@ -1307,6 +1415,7 @@ final class Analyser extends Parser { } // switch } + @SuppressWarnings("unused") protected final void setOptimizedInfoFromTree(final Node node) { final NodeOptInfo opt = new NodeOptInfo(); final OptEnvironment oenv = new OptEnvironment(); @@ -1347,7 +1456,9 @@ final class Analyser extends Parser { regex.setSubAnchor(opt.map.anchor); } else { regex.subAnchor |= opt.anchor.leftAnchor & AnchorType.BEGIN_LINE; - if (opt.length.max == 0) regex.subAnchor |= opt.anchor.rightAnchor & AnchorType.END_LINE; + if (opt.length.max == 0) { + regex.subAnchor |= opt.anchor.rightAnchor & AnchorType.END_LINE; + } } if (Config.DEBUG_COMPILE || Config.DEBUG_MATCH) { diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/ApplyCaseFold.java b/src/jdk/nashorn/internal/runtime/regexp/joni/ApplyCaseFold.java index 0a6d4b05..5ecfec4f 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/ApplyCaseFold.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/ApplyCaseFold.java @@ -24,7 +24,7 @@ import jdk.nashorn.internal.runtime.regexp.joni.ast.CClassNode; final class ApplyCaseFold { // i_apply_case_fold - public void apply(final int from, final int to, final Object o) { + public static void apply(final int from, final int to, final Object o) { final ApplyCaseFoldArg arg = (ApplyCaseFoldArg)o; final ScanEnvironment env = arg.env; @@ -45,7 +45,9 @@ final class ApplyCaseFold { } else { if (inCC) { if (to >= BitSet.SINGLE_BYTE_SIZE) { - if (cc.isNot()) cc.clearNotFlag(); + if (cc.isNot()) { + cc.clearNotFlag(); + } cc.addCodeRange(env, to, to); } else { if (cc.isNot()) { diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/ApplyCaseFoldArg.java b/src/jdk/nashorn/internal/runtime/regexp/joni/ApplyCaseFoldArg.java index c199bea7..ce25af62 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/ApplyCaseFoldArg.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/ApplyCaseFoldArg.java @@ -22,6 +22,7 @@ package jdk.nashorn.internal.runtime.regexp.joni; import jdk.nashorn.internal.runtime.regexp.joni.ast.CClassNode; import jdk.nashorn.internal.runtime.regexp.joni.ast.ConsAltNode; +@SuppressWarnings("javadoc") public final class ApplyCaseFoldArg { final ScanEnvironment env; final CClassNode cc; diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/ArrayCompiler.java b/src/jdk/nashorn/internal/runtime/regexp/joni/ArrayCompiler.java index 294113e3..0e789333 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/ArrayCompiler.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/ArrayCompiler.java @@ -24,7 +24,6 @@ import static jdk.nashorn.internal.runtime.regexp.joni.Option.isDynamic; import static jdk.nashorn.internal.runtime.regexp.joni.Option.isIgnoreCase; import static jdk.nashorn.internal.runtime.regexp.joni.Option.isMultiline; import static jdk.nashorn.internal.runtime.regexp.joni.ast.QuantifierNode.isRepeatInfinite; - import jdk.nashorn.internal.runtime.regexp.joni.ast.AnchorNode; import jdk.nashorn.internal.runtime.regexp.joni.ast.BackRefNode; import jdk.nashorn.internal.runtime.regexp.joni.ast.CClassNode; @@ -98,15 +97,15 @@ final class ArrayCompiler extends Compiler { } while ((aln = aln.cdr) != null); } - private boolean isNeedStrLenOpExact(final int op) { + private static boolean isNeedStrLenOpExact(final int op) { return op == OPCode.EXACTN || op == OPCode.EXACTN_IC; } - private boolean opTemplated(final int op) { + private static boolean opTemplated(final int op) { return isNeedStrLenOpExact(op); } - private int selectStrOpcode(final int strLength, final boolean ignoreCase) { + private static int selectStrOpcode(final int strLength, final boolean ignoreCase) { int op; if (ignoreCase) { @@ -139,7 +138,7 @@ final class ArrayCompiler extends Compiler { compileTree(node); if (emptyInfo != 0) { - switch(emptyInfo) { + switch (emptyInfo) { case TargetInfo.IS_EMPTY: addOpcode(OPCode.NULL_CHECK_END); break; @@ -149,13 +148,15 @@ final class ArrayCompiler extends Compiler { case TargetInfo.IS_EMPTY_REC: addOpcode(OPCode.NULL_CHECK_END_MEMST_PUSH); break; + default: + break; } // switch addMemNum(savedNumNullCheck); /* NULL CHECK ID */ } } - private int addCompileStringlength(final char[] chars, final int p, final int strLength, final boolean ignoreCase) { + private static int addCompileStringlength(final char[] chars, final int p, final int strLength, final boolean ignoreCase) { final int op = selectStrOpcode(strLength, ignoreCase); int len = OPSize.OPCODE; @@ -163,7 +164,9 @@ final class ArrayCompiler extends Compiler { // string length, template index, template string pointer len += OPSize.LENGTH + OPSize.INDEX + OPSize.INDEX; } else { - if (isNeedStrLenOpExact(op)) len += OPSize.LENGTH; + if (isNeedStrLenOpExact(op)) { + len += OPSize.LENGTH; + } len += strLength; } return len; @@ -187,9 +190,11 @@ final class ArrayCompiler extends Compiler { } } - private int compileLengthStringNode(final Node node) { + private static int compileLengthStringNode(final Node node) { final StringNode sn = (StringNode)node; - if (sn.length() <= 0) return 0; + if (sn.length() <= 0) { + return 0; + } final boolean ambig = sn.isAmbig(); int p, prev; @@ -210,8 +215,10 @@ final class ArrayCompiler extends Compiler { return rlen; } - private int compileLengthStringRawNode(final StringNode sn) { - if (sn.length() <= 0) return 0; + private static int compileLengthStringRawNode(final StringNode sn) { + if (sn.length() <= 0) { + return 0; + } return addCompileStringlength(sn.chars, sn.p, sn.length(), false); } @@ -220,8 +227,10 @@ final class ArrayCompiler extends Compiler { addInts(mbuf.p, mbuf.used); } - private int compileLengthCClassNode(final CClassNode cc) { - if (cc.isShare()) return OPSize.OPCODE + OPSize.POINTER; + private static int compileLengthCClassNode(final CClassNode cc) { + if (cc.isShare()) { + return OPSize.OPCODE + OPSize.POINTER; + } int len; if (cc.mbuf == null) { @@ -360,9 +369,8 @@ final class ArrayCompiler extends Compiler { if (qn.greedy && infinite) { if (qn.nextHeadExact != null) { return OPSize.ANYCHAR_STAR_PEEK_NEXT + tlen * qn.lower; - } else { - return OPSize.ANYCHAR_STAR + tlen * qn.lower; } + return OPSize.ANYCHAR_STAR + tlen * qn.lower; } } @@ -425,14 +433,13 @@ final class ArrayCompiler extends Compiler { final StringNode sn = (StringNode)qn.nextHeadExact; addChars(sn.chars, sn.p, 1); return; + } + if (isMultiline(regex.options)) { + addOpcode(OPCode.ANYCHAR_ML_STAR); } else { - if (isMultiline(regex.options)) { - addOpcode(OPCode.ANYCHAR_ML_STAR); - } else { - addOpcode(OPCode.ANYCHAR_STAR); - } - return; + addOpcode(OPCode.ANYCHAR_STAR); } + return; } int modTLen; @@ -510,9 +517,8 @@ final class ArrayCompiler extends Compiler { if (isDynamic(prev ^ node.option)) { return OPSize.SET_OPTION_PUSH + OPSize.SET_OPTION + OPSize.FAIL + tlen + OPSize.SET_OPTION; - } else { - return tlen; } + return tlen; } @Override @@ -675,13 +681,15 @@ final class ArrayCompiler extends Compiler { break; case AnchorType.WORD_BEGIN: - if (Config.USE_WORD_BEGIN_END) + if (Config.USE_WORD_BEGIN_END) { addOpcode(OPCode.WORD_BEGIN); + } break; case AnchorType.WORD_END: - if (Config.USE_WORD_BEGIN_END) + if (Config.USE_WORD_BEGIN_END) { addOpcode(OPCode.WORD_END); + } break; case AnchorType.PREC_READ: @@ -701,7 +709,9 @@ final class ArrayCompiler extends Compiler { addOpcode(OPCode.LOOK_BEHIND); if (node.charLength < 0) { n = analyser.getCharLengthTree(node.target); - if (analyser.returnCode != 0) newSyntaxException(ERR_INVALID_LOOK_BEHIND_PATTERN); + if (analyser.returnCode != 0) { + newSyntaxException(ERR_INVALID_LOOK_BEHIND_PATTERN); + } } else { n = node.charLength; } @@ -714,7 +724,9 @@ final class ArrayCompiler extends Compiler { addOpcodeRelAddr(OPCode.PUSH_LOOK_BEHIND_NOT, len + OPSize.FAIL_LOOK_BEHIND_NOT); if (node.charLength < 0) { n = analyser.getCharLengthTree(node.target); - if (analyser.returnCode != 0) newSyntaxException(ERR_INVALID_LOOK_BEHIND_PATTERN); + if (analyser.returnCode != 0) { + newSyntaxException(ERR_INVALID_LOOK_BEHIND_PATTERN); + } } else { n = node.charLength; } @@ -796,7 +808,9 @@ final class ArrayCompiler extends Compiler { private void ensure(final int size) { if (size >= code.length) { int length = code.length << 1; - while (length <= size) length <<= 1; + while (length <= size) { + length <<= 1; + } final int[]tmp = new int[length]; System.arraycopy(code, 0, tmp, 0, code.length); code = tmp; @@ -829,11 +843,14 @@ final class ArrayCompiler extends Compiler { regex.operands[regex.operandLength++] = o; } - private void addChars(final char[] chars, int p ,final int length) { + private void addChars(final char[] chars, final int pp ,final int length) { ensure(codeLength + length); + int p = pp; final int end = p + length; - while (p < end) code[codeLength++] = chars[p++]; + while (p < end) { + code[codeLength++] = chars[p++]; + } } private void addInts(final int[]ints, final int length) { @@ -876,6 +893,9 @@ final class ArrayCompiler extends Compiler { case OPCode.CALL: case OPCode.RETURN: // it will appear only with CALL though regex.stackNeeded = true; + break; + default: + break; } } diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/BitSet.java b/src/jdk/nashorn/internal/runtime/regexp/joni/BitSet.java index 2aac96a8..2747c24c 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/BitSet.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/BitSet.java @@ -19,6 +19,7 @@ */ package jdk.nashorn.internal.runtime.regexp.joni; +@SuppressWarnings("javadoc") public final class BitSet { static final int BITS_PER_BYTE = 8; public static final int SINGLE_BYTE_SIZE = (1 << BITS_PER_BYTE); @@ -34,7 +35,9 @@ public final class BitSet { final StringBuilder buffer = new StringBuilder(); buffer.append("BitSet"); for (int i=0; i<SINGLE_BYTE_SIZE; i++) { - if ((i % (SINGLE_BYTE_SIZE / BITS_TO_STRING_WRAP)) == 0) buffer.append("\n "); + if ((i % (SINGLE_BYTE_SIZE / BITS_TO_STRING_WRAP)) == 0) { + buffer.append("\n "); + } buffer.append(at(i) ? "1" : "0"); } return buffer.toString(); @@ -53,44 +56,62 @@ public final class BitSet { } public void clear() { - for (int i=0; i<BITSET_SIZE; i++) bits[i]=0; + for (int i=0; i<BITSET_SIZE; i++) { + bits[i]=0; + } } public boolean isEmpty() { for (int i=0; i<BITSET_SIZE; i++) { - if (bits[i] != 0) return false; + if (bits[i] != 0) { + return false; + } } return true; } public void setRange(final int from, final int to) { - for (int i=from; i<=to && i < SINGLE_BYTE_SIZE; i++) set(i); + for (int i=from; i<=to && i < SINGLE_BYTE_SIZE; i++) { + set(i); + } } public void invert() { - for (int i=0; i<BITSET_SIZE; i++) bits[i] = ~bits[i]; + for (int i=0; i<BITSET_SIZE; i++) { + bits[i] = ~bits[i]; + } } public void invertTo(final BitSet to) { - for (int i=0; i<BITSET_SIZE; i++) to.bits[i] = ~bits[i]; + for (int i=0; i<BITSET_SIZE; i++) { + to.bits[i] = ~bits[i]; + } } public void and(final BitSet other) { - for (int i=0; i<BITSET_SIZE; i++) bits[i] &= other.bits[i]; + for (int i=0; i<BITSET_SIZE; i++) { + bits[i] &= other.bits[i]; + } } public void or(final BitSet other) { - for (int i=0; i<BITSET_SIZE; i++) bits[i] |= other.bits[i]; + for (int i=0; i<BITSET_SIZE; i++) { + bits[i] |= other.bits[i]; + } } public void copy(final BitSet other) { - for (int i=0; i<BITSET_SIZE; i++) bits[i] = other.bits[i]; + for (int i=0; i<BITSET_SIZE; i++) { + bits[i] = other.bits[i]; + } } public int numOn() { int num = 0; for (int i=0; i<SINGLE_BYTE_SIZE; i++) { - if (at(i)) num++; + if (at(i)) { + num++; + } } return num; } @@ -99,9 +120,12 @@ public final class BitSet { return 1 << (pos % SINGLE_BYTE_SIZE); } - private static int log2(int n){ + private static int log2(final int np) { int log = 0; - while ((n >>>= 1) != 0) log++; + int n = np; + while ((n >>>= 1) != 0) { + log++; + } return log; } diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/BitStatus.java b/src/jdk/nashorn/internal/runtime/regexp/joni/BitStatus.java index 25806a51..91cf7198 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/BitStatus.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/BitStatus.java @@ -34,7 +34,8 @@ final class BitStatus { return (n < BIT_STATUS_BITS_NUM ? stats & (1 << n) : (stats & 1)) != 0; } - public static int bsOnAt(int stats, final int n) { + public static int bsOnAt(final int statsp, final int n) { + int stats = statsp; if (n < BIT_STATUS_BITS_NUM) { stats |= (1 << n); } else { @@ -43,12 +44,7 @@ final class BitStatus { return stats; } - public static int bsOnOff(int v, final int f, final boolean negative) { - if (negative) { - v &= ~f; - } else { - v |= f; - } - return v; + public static int bsOnOff(final int v, final int f, final boolean negative) { + return negative ? (v & ~f) : (v | f); } } diff --git a/src/jdk/nashorn/internal/runtime/regexp/joni/ByteCodeMachine.java b/src/jdk/nashorn/internal/runtime/regexp/joni/ByteCodeMachine.java index ab28f93c..30cfe907 100644 --- a/src/jdk/nashorn/internal/runtime/regexp/joni/ByteCodeMachine.java +++ b/src/jdk/nashorn/internal/runtime/regexp/joni/ByteCodeMachine.java @@ -27,10 +27,8 @@ import static jdk.nashorn.internal.runtime.regexp.joni.Option.isFindNotEmpty; import static jdk.nashorn.internal.runtime.regexp.joni.Option.isNotBol; import static jdk.nashorn.internal.runtime.regexp.joni.Option.isNotEol; import static jdk.nashorn.internal.runtime.regexp.joni.Option.isPosixRegion; - import jdk.nashorn.internal.runtime.regexp.joni.ast.CClassNode; import jdk.nashorn.internal.runtime.regexp.joni.constants.OPCode; -import jdk.nashorn.internal.runtime.regexp.joni.constants.OPSize; import jdk.nashorn.internal.runtime.regexp.joni.encoding.IntHolder; import jdk.nashorn.internal.runtime.regexp.joni.exception.ErrorMessages; import jdk.nashorn.internal.runtime.regexp.joni.exception.InternalException; @@ -52,8 +50,8 @@ class ByteCodeMachine extends StackMachine { this.code = regex.code; } - private boolean stringCmpIC(final int caseFlodFlag, int s1, final IntHolder ps2, final int mbLen, final int textEnd) { - + private boolean stringCmpIC(final int caseFlodFlag, final int s1p, final IntHolder ps2, final int mbLen, final int textEnd) { + int s1 = s1p; int s2 = ps2.value; final int end1 = s1 + mbLen; @@ -83,12 +81,16 @@ class ByteCodeMachine extends StackMachine { Config.log.printf("%4d", (s - str)).print("> \""); int q, i; for (i=0, q=s; i<7 && q<end && s>=0; i++) { - if (q < end) Config.log.print(new String(new char[]{chars[q++]})); + if (q < end) { + Config.log.print(new String(new char[]{chars[q++]})); + } + } + final String string = q < end ? "...\"" : "\""; + q += string.length(); + Config.log.print(string); + for (i=0; i<20-(q-s);i++) { + Config.log.print(" "); } - final String str = q < end ? "...\"" : "\""; - q += str.length(); - Config.log.print(str); - for (i=0; i<20-(q-s);i++) Config.log.print(" "); final StringBuilder sb = new StringBuilder(); new ByteCodePrinter(regex).compiledByteCodeToString(sb, ip); Config.log.println(sb.toString()); @@ -96,28 +98,34 @@ class ByteCodeMachine extends StackMachine { } @Override - protected final int matchAt(final int range, final int sstart, final int sprev) { - this.range = range; - this.sstart = sstart; - this.sprev = sprev; + protected final int matchAt(final int r, final int ss, final int sp) { + this.range = r; + this.sstart = ss; + this.sprev = sp; stk = 0; ip = 0; - if (Config.DEBUG_MATCH) debugMatchBegin(); + if (Config.DEBUG_MATCH) { + debugMatchBegin(); + } init(); bestLen = -1; - s = sstart; + s = ss; - final int[]code = this.code; + final int[] c = this.code; while (true) { - if (Config.DEBUG_MATCH) debugMatchLoop(); + if (Config.DEBUG_MATCH) { + debugMatchLoop(); + } sbegin = s; - switch (code[ip++]) { - case OPCode.END: if (opEnd()) return finish(); break; + switch (c[ip++]) { + case OPCode.END: if (opEnd()) { + return finish(); + } break; case OPCode.EXACT1: opExact1(); break; case OPCode.EXACT2: opExact2(); continue; case OPCode.EXACT3: opExact3(); continue; @@ -358,10 +366,14 @@ class ByteCodeMachine extends StackMachine { final char[] bs = regex.templates[code[ip++]]; int ps = code[ip++]; - while (tlen-- > 0) if (bs[ps++] != chars[s++]) {opFail(); return;} + while (tlen-- > 0) { + if (bs[ps++] != chars[s++]) {opFail(); return;} + } } else { - while (tlen-- > 0) if (code[ip++] != chars[s++]) {opFail(); return;} + while (tlen-- > 0) { + if (code[ip++] != chars[s++]) {opFail(); return;} + } } sprev = s - 1; } @@ -380,10 +392,14 @@ class ByteCodeMachine extends StackMachine { final char[] bs = regex.templates[code[ip++]]; int ps = code[ip++]; - while (tlen-- > 0) if (bs[ps++] != EncodingHelper.toLowerCase(chars[s++])) {opFail(); return;} + while (tlen-- > 0) { + if (bs[ps++] != EncodingHelper.toLowerCase(chars[s++])) {opFail(); return;} + } } else { - while (tlen-- > 0) if (code[ip++] != EncodingHelper.toLowerCase(chars[s++])) {opFail(); return;} + while (tlen-- > 0) { + if (code[ip++] != EncodingHelper.toLowerCase(chars[s++])) {opFail(); return;} + } } sprev = s - 1; } @@ -402,11 +418,15 @@ class ByteCodeMachine extends StackMachine { private boolean isInClassMB() { final int tlen = code[ip++]; - if (s >= range) return false; + if (s >= range) { + return false; + } final int ss = s; s++; final int c = chars[ss]; - if (!EncodingHelper.isInCodeRange(code, ip, c)) return false; + if (!EncodingHelper.isInCodeRange(code, ip, c)) { + return false; + } ip += tlen; return true; } @@ -444,7 +464,9 @@ class ByteCodeMachine extends StackMachine { final int tlen = code[ip++]; if (!(s + 1 <= range)) { - if (s >= range) return false; + if (s >= range) { + return false; + } s = end; ip += tlen; return true; @@ -454,7 +476,9 @@ class ByteCodeMachine extends StackMachine { s++; final int c = chars[ss]; - if (EncodingHelper.isInCodeRange(code, ip, c)) return false; + if (EncodingHelper.isInCodeRange(code, ip, c)) { + return false; + } ip += tlen; return true; } @@ -511,10 +535,10 @@ class ByteCodeMachine extends StackMachine { } private void opAnyCharStar() { - final char[] chars = this.chars; + final char[] ch = this.chars; while (s < range) { pushAlt(ip, s, sprev); - if (isNewLine(chars, s, end)) {opFail(); return;} + if (isNewLine(ch, s, end)) {opFail(); return;} sprev = s; s++; } @@ -532,11 +556,13 @@ class ByteCodeMachine extends StackMachine { private void opAnyCharStarPeekNext() { final char c = (char)code[ip]; - final char[] chars = this.chars; + final char[] ch = this.chars; while (s < range) { - final char b = chars[s]; - if (c == b) pushAlt(ip + 1, s, sprev); + final char b = ch[s]; + if (c == b) { + pushAlt(ip + 1, s, sprev); + } if (isNewLine(b)) {opFail(); return;} sprev = s; s++; @@ -547,10 +573,12 @@ class ByteCodeMachine extends StackMachine { private void opAnyCharMLStarPeekNext() { final char c = (char)code[ip]; - final char[] chars = this.chars; + final char[] ch = this.chars; while (s < range) { - if (c == chars[s]) pushAlt(ip + 1, s, sprev); + if (c == ch[s]) { + pushAlt(ip + 1, s, sprev); + } sprev = s; s++; } @@ -592,29 +620,39 @@ class ByteCodeMachine extends StackMachine { private void opWordBegin() { if (s < range && EncodingHelper.isWord(chars[s])) { - if (s == str || !EncodingHelper.isWord(chars[sprev])) return; + if (s == str || !EncodingHelper.isWord(chars[sprev])) { + return; + } } opFail(); } private void opWordEnd() { if (s != str && EncodingHelper.isWord(chars[sprev])) { - if (s == end || !EncodingHelper.isWord(chars[s])) return; + if (s == end || !EncodingHelper.isWord(chars[s])) { + return; + } } opFail(); } private void opBeginBuf() { - if (s != str) opFail(); + if (s != str) { + opFail(); + } } private void opEndBuf() { - if (s != end) opFail(); + if (s != end) { + opFail(); + } } private void opBeginLine() { if (s == str) { - if (isNotBol(msaOptions)) opFail(); + if (isNotBol(msaOptions)) { + opFail(); + } return; } else if (isNewLine(chars, sprev, end) && s != end) { return; @@ -626,13 +664,16 @@ class ByteCodeMachine extends StackMachine { if (s == end) { if (Config.USE_NEWLINE_AT_END_OF_STRING_HAS_EMPTY_LINE) { if (str == end || !isNewLine(chars, sprev, end)) { - if (isNotEol(msaOptions)) opFail(); + if (isNotEol(msaOptions)) { + opFail(); + } } return; - } else { - if (isNotEol(msaOptions)) opFail(); - return; } + if (isNotEol(msaOptions)) { + opFail(); + } + return; } else if (isNewLine(chars, s, end)) { return; } @@ -643,13 +684,16 @@ class ByteCodeMachine extends StackMachine { if (s == end) { if (Config.USE_NEWLINE_AT_END_OF_STRING_HAS_EMPTY_LINE) { if (str == end || !isNewLine(chars, sprev, end)) { - if (isNotEol(msaOptions)) opFail(); + if (isNotEol(msaOptions)) { + opFail(); + } } return; - } else { - if (isNotEol(msaOptions)) opFail(); - return; } + if (isNotEol(msaOptions)) { + opFail(); + } + return; } else if (isNewLine(chars, s, end) && s + 1 == end) { return; } @@ -657,7 +701,9 @@ class ByteCodeMachine extends StackMachine { } private void opBeginPosition() { - if (s != msaStart) opFail(); + if (s != msaStart) { + opFail(); + } } private void opMemoryStartPush() { @@ -726,11 +772,15 @@ class ByteCodeMachine extends StackMachine { sprev = s; // STRING_CMP - while(n-- > 0) if (chars[pstart++] != chars[s++]) {opFail(); return;} + while(n-- > 0) { + if (chars[pstart++] != chars[s++]) {opFail(); return;} + } // beyond string check if (sprev < range) { - while (sprev + 1 < s) sprev++; + while (sprev + 1 < s) { + sprev++; + } } } @@ -764,7 +814,9 @@ class ByteCodeMachine extends StackMachine { s = value; // if (sprev < chars.length) - while (sprev + 1 < s) sprev++; + while (sprev + 1 < s) { + sprev++; + } } private void opBackRefMulti() { @@ -773,7 +825,9 @@ class ByteCodeMachine extends StackMachine { int i; loop:for (i=0; i<tlen; i++) { final int mem = code[ip++]; - if (backrefInvalid(mem)) continue; + if (backrefInvalid(mem)) { + continue; + } int pstart = backrefStart(mem); final int pend = backrefEnd(mem); @@ -785,14 +839,18 @@ class ByteCodeMachine extends StackMachine { int swork = s; while (n-- > 0) { - if (chars[pstart++] != chars[swork++]) continue loop; + if (chars[pstart++] != chars[swork++]) { + continue loop; + } } s = swork; // beyond string check if (sprev < range) { - while (sprev + 1 < s) sprev++; + while (sprev + 1 < s) { + sprev++; + } } ip += tlen - i - 1; // * SIZE_MEMNUM (1) @@ -807,7 +865,9 @@ class ByteCodeMachine extends StackMachine { int i; loop:for (i=0; i<tlen; i++) { final int mem = code[ip++]; - if (backrefInvalid(mem)) continue; + if (backrefInvalid(mem)) { + continue; + } final int pstart = backrefStart(mem); final int pend = backrefEnd(mem); @@ -818,11 +878,16 @@ class ByteCodeMachine extends StackMachine { sprev = s; value = s; - if (!stringCmpIC(regex.caseFoldFlag, pstart, this, n, end)) continue loop; // STRING_CMP_VALUE_IC + if (!stringCmpIC(regex.caseFoldFlag, pstart, this, n, end)) + { + continue loop; // STRING_CMP_VALUE_IC + } s = value; // if (sprev < chars.length) - while (sprev + 1 < s) sprev++; + while (sprev + 1 < s) { + sprev++; + } ip += tlen - i - 1; // * SIZE_MEMNUM (1) break; /* success */ @@ -830,10 +895,12 @@ class ByteCodeMachine extends StackMachine { if (i == tlen) {opFail(); return;} } - private boolean memIsInMemp(final int mem, final int num, int memp) { - for (int i=0; i<num; i++) { + private boolean memIsInMemp(final int mem, final int num, final int mempp) { + for (int i=0, memp = mempp; i<num; i++) { final int m = code[memp++]; - if (mem == m) return true; + if (mem == m) { + return true; + } } return false; } @@ -857,7 +924,9 @@ class ByteCodeMachine extends StackMachine { if (memIsInMemp(e.getMemNum(), memNum, memp)) { final int pstart = e.getMemPStr(); if (pend != -1) { - if (pend - pstart > end - s) return false; /* or goto next_mem; */ + if (pend - pstart > end - s) { + return false; /* or goto next_mem; */ + } int p = pstart; value = s; @@ -867,7 +936,9 @@ class ByteCodeMachine extends StackMachine { } } else { while (p < pend) { - if (chars[p++] != chars[value++]) return false; /* or goto next_mem; */ + if (chars[p++] != chars[value++]) { + return false; /* or goto next_mem; */ + } } } s = value; @@ -893,24 +964,15 @@ class ByteCodeMachine extends StackMachine { sprev = s; if (backrefMatchAtNestedLevel(ic != 0, regex.caseFoldFlag, level, tlen, ip)) { // (s) and (end) implicit - while (sprev + 1 < s) sprev++; + while (sprev + 1 < s) { + sprev++; + } |