From 4c69f78de8aa7f05f8d362619c4c82adfd84c0da Mon Sep 17 00:00:00 2001 From: Zalim Bashorov Date: Fri, 4 Dec 2020 01:59:25 +0300 Subject: [PATCH] [JS] Replace J2V8 based ScriptEngine with a process-based version The main advantage of this is that we can use a newer and official build of V8. Also, with new infra, we can use other JS engines. Other changes: * ScriptEngine API is simplified and documented. * Introduce ScriptEngineWithTypedResult with typed `eval`, mostly for JsReplEvaluator and JsScriptEvaluator. * J2V8 version is completely removed. * Use new ScriptEngineV8 everywhere by default. * System property `kotlin.js.useNashorn` switches to Nashorn in all tests. --- js/js.engines/build.gradle.kts | 2 +- .../js/engine/ProcessBasedScriptEngine.kt | 96 +++++++++++++ .../kotlin/js/engine/ScriptEngine.kt | 50 +++++-- .../kotlin/js/engine/ScriptEngineNashorn.kt | 38 +++-- .../kotlin/js/engine/ScriptEngineV8.kt | 101 ++----------- .../org/jetbrains/kotlin/js/engine/repl.js | 101 +++++++++++++ .../jetbrains/kotlin/js/test/BasicBoxTest.kt | 11 +- .../jetbrains/kotlin/js/test/JsTestChecker.kt | 135 ++++++++---------- .../js/test/optimizer/BasicOptimizerTest.kt | 19 +-- .../semantics/AbstractWebDemoExamplesTest.kt | 9 +- .../js/test/semantics/MultiModuleOrderTest.kt | 18 +-- .../semantics/WebDemoCanvasExamplesTest.java | 26 +--- .../scripting/repl/js/JsReplEvaluator.kt | 4 +- .../kotlin/scripting/repl/js/JsReplUtils.kt | 8 +- .../scripting/repl/js/JsScriptEvaluator.kt | 4 +- 15 files changed, 357 insertions(+), 265 deletions(-) create mode 100644 js/js.engines/src/org/jetbrains/kotlin/js/engine/ProcessBasedScriptEngine.kt create mode 100644 js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js diff --git a/js/js.engines/build.gradle.kts b/js/js.engines/build.gradle.kts index 66402b7a5fb..bcbd4322986 100644 --- a/js/js.engines/build.gradle.kts +++ b/js/js.engines/build.gradle.kts @@ -8,7 +8,7 @@ dependencies { compile(project(":compiler:util")) compile(project(":js:js.ast")) compile(project(":js:js.translator")) - compileOnly(intellijCoreDep()) { includeJars("intellij-core") } + compile(intellijCoreDep()) { includeJars("intellij-core") } } sourceSets { diff --git a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ProcessBasedScriptEngine.kt b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ProcessBasedScriptEngine.kt new file mode 100644 index 00000000000..4cf663f5e37 --- /dev/null +++ b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ProcessBasedScriptEngine.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.js.engine + +import com.intellij.openapi.util.text.StringUtil + +private val LINE_SEPARATOR = System.getProperty("line.separator")!! +private val END_MARKER = "$LINE_SEPARATOR" + +abstract class ProcessBasedScriptEngine( + private val executablePath: String +) : ScriptEngine { + + private var process: Process? = null + private val buffer = ByteArray(1024) + + override fun eval(script: String): String { + val vm = getOrCreateProcess() + + val stdin = vm.outputStream + val stdout = vm.inputStream + val stderr = vm.errorStream + + val writer = stdin.writer() + writer.write(StringUtil.convertLineSeparators(script, "\\n") + "\n") + writer.flush() + + val out = StringBuilder() + + while (vm.isAlive) { + val n = stdout.available() + if (n == 0) continue + + val count = stdout.read(buffer) + + val s = String(buffer, 0, count) + out.append(s) + + if (out.endsWith(END_MARKER)) break + } + + if (stderr.available() > 0) { + val err = StringBuilder() + + while (vm.isAlive && stderr.available() > 0) { + val count = stderr.read(buffer) + val s = String(buffer, 0, count) + err.append(s) + } + + error("ERROR:\n$err\nOUTPUT:\n$out") + } + + return out.removeSuffix(END_MARKER).removeSuffix(LINE_SEPARATOR).toString() + } + + override fun loadFile(path: String) { + eval("load('${path.replace('\\', '/')}');") + } + + override fun reset() { + eval("!reset") + } + + override fun saveGlobalState() { + eval("!saveGlobalState") + } + + override fun restoreGlobalState() { + eval("!restoreGlobalState") + } + + override fun release() { + process?.destroy() + process = null + } + + private fun getOrCreateProcess(): Process { + val p = process + + if (p != null && p.isAlive) return p + + process = null + + val builder = ProcessBuilder( + executablePath, + "js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js", + ) + return builder.start().also { + process = it + } + } +} diff --git a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngine.kt b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngine.kt index 1b0fb773a4c..d307db8bb75 100644 --- a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngine.kt +++ b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngine.kt @@ -1,18 +1,48 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.js.engine interface ScriptEngine { - fun eval(script: String): T - fun evalVoid(script: String) - fun callMethod(obj: Any, name: String, vararg args: Any?): T - fun loadFile(path: String) - fun release() - fun releaseObject(t: T) + fun eval(script: String): String - fun saveState() - fun restoreState() -} \ No newline at end of file + // TODO Add API to load few files at once? + fun loadFile(path: String) + + /** + * Performs truly reset of the engine state. + * */ + fun reset() + + /** + * Saves current state of global object. + * + * See also [restoreGlobalState] + */ + fun saveGlobalState() + + /** + * Restores global object from the last saved state. + * + * See also [saveGlobalState] + */ + fun restoreGlobalState() + + + /** + * Release held resources. + * + * Must be called explicitly before an object is garbage collected to avoid leaking resources. + */ + fun release() +} + +interface ScriptEngineWithTypedResult : ScriptEngine { + fun evalWithTypedResult(script: String): R +} + +fun ScriptEngine.loadFiles(files: List) { + files.forEach { loadFile(it) } +} diff --git a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngineNashorn.kt b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngineNashorn.kt index c5e86590824..270c9a78175 100644 --- a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngineNashorn.kt +++ b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngineNashorn.kt @@ -1,53 +1,49 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") package org.jetbrains.kotlin.js.engine +import jdk.nashorn.api.scripting.NashornScriptEngine import jdk.nashorn.api.scripting.NashornScriptEngineFactory import jdk.nashorn.internal.runtime.ScriptRuntime -import javax.script.Invocable -class ScriptEngineNashorn : ScriptEngine { +class ScriptEngineNashorn : ScriptEngineWithTypedResult { private var savedState: Map? = null // TODO use "-strict" private val myEngine = NashornScriptEngineFactory().getScriptEngine("--language=es5", "--no-java", "--no-syntax-extensions") - @Suppress("UNCHECKED_CAST") - override fun eval(script: String): T { - return myEngine.eval(script) as T - } - - override fun evalVoid(script: String) { - myEngine.eval(script) - } + override fun eval(script: String): String = evalWithTypedResult(script).toString() @Suppress("UNCHECKED_CAST") - override fun callMethod(obj: Any, name: String, vararg args: Any?): T { - return (myEngine as Invocable).invokeMethod(obj, name, *args) as T + override fun evalWithTypedResult(script: String): R { + return myEngine.eval(script) as R } override fun loadFile(path: String) { - evalVoid("load('${path.replace('\\', '/')}');") + eval("load('${path.replace('\\', '/')}');") } - override fun release() {} - override fun releaseObject(t: T) {} + override fun reset() { + throw UnsupportedOperationException() + } + private fun getGlobalState(): MutableMap = evalWithTypedResult("this") - private fun getGlobalState(): MutableMap = eval("this") - - override fun saveState() { + override fun saveGlobalState() { savedState = getGlobalState().toMap() } - override fun restoreState() { + override fun restoreGlobalState() { val globalState = getGlobalState() val originalState = savedState!! for (key in globalState.keys) { globalState[key] = originalState[key] ?: ScriptRuntime.UNDEFINED } } -} \ No newline at end of file + + override fun release() { + } +} diff --git a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngineV8.kt b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngineV8.kt index 398a2ded22f..511d7420a35 100644 --- a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngineV8.kt +++ b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ScriptEngineV8.kt @@ -1,98 +1,23 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.js.engine -import com.eclipsesource.v8.V8 -import com.eclipsesource.v8.V8Array -import com.eclipsesource.v8.V8Object -import com.eclipsesource.v8.utils.V8ObjectUtils -import java.io.File +class ScriptEngineV8 : ProcessBasedScriptEngine(System.getProperty("javascript.engine.path.V8")) -class ScriptEngineV8(LIBRARY_PATH_BASE: String) : ScriptEngine { - - override fun releaseObject(t: T) { - (t as? V8Object)?.release() - } - - private var savedState: List? = null - - override fun restoreState() { - val scriptBuilder = StringBuilder() - - val globalState = getGlobalPropertyNames() - val originalState = savedState!! - for (key in globalState) { - if (key !in originalState) { - scriptBuilder.append("this['$key'] = void 0;\n") - } +fun main() { +// System.setProperty("javascript.engine.path.V8", "") + val vm = ScriptEngineV8() + println("Welcome!") + while (true) { + print("> ") + val t = readLine() + try { + println(vm.eval(t!!)) + } catch (e: Throwable) { + System.err.println(e) } - evalVoid(scriptBuilder.toString()) - } - - private fun getGlobalPropertyNames(): List { - val v8Array = eval("Object.getOwnPropertyNames(this)") - @Suppress("UNCHECKED_CAST") val javaArray = V8ObjectUtils.toList(v8Array) as List - v8Array.release() - return javaArray - } - - override fun saveState() { - if (savedState == null) { - savedState = getGlobalPropertyNames() - } - } - - private val myRuntime: V8 = V8.createV8Runtime("global", LIBRARY_PATH_BASE) - - @Suppress("UNCHECKED_CAST") - override fun eval(script: String): T { - return myRuntime.executeScript(script) as T - } - - override fun evalVoid(script: String) { - return myRuntime.executeVoidScript(script) - } - - @Suppress("UNCHECKED_CAST") - override fun callMethod(obj: Any, name: String, vararg args: Any?): T { - if (obj !is V8Object) { - throw Exception("InteropV8 can deal only with V8Object") - } - - val runtimeArray = V8Array(myRuntime) - val result = obj.executeFunction(name, runtimeArray) as T - runtimeArray.release() - return result - } - - override fun loadFile(path: String) { - myRuntime.executeVoidScript(File(path).bufferedReader().use { it.readText() }, path, 0) - } - - override fun release() { - myRuntime.release() } } - -class ScriptEngineV8Lazy(LIBRARY_PATH_BASE: String) : ScriptEngine { - override fun eval(script: String) = engine.eval(script) - - override fun saveState() = engine.saveState() - - override fun evalVoid(script: String) = engine.evalVoid(script) - - override fun callMethod(obj: Any, name: String, vararg args: Any?) = engine.callMethod(obj, name, args) - - override fun loadFile(path: String) = engine.loadFile(path) - - override fun release() = engine.release() - - override fun releaseObject(t: T) = engine.releaseObject(t) - - override fun restoreState() = engine.restoreState() - - private val engine by lazy { ScriptEngineV8(LIBRARY_PATH_BASE) } -} \ No newline at end of file diff --git a/js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js b/js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js new file mode 100644 index 00000000000..87f5158e964 --- /dev/null +++ b/js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +// js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js + +/* +Some of non-standard APIs available in standalone JS engines: + + v8 sm jsc +load + + + load and evaluate a file +print + + + print to stdout +printErr + + + print to stderr +read + + + read a file as a text (v8, sm, jsc) or binary (sm, jsc) +readline + + + read line from stdin +readbuffer + - - read a binary file in v8 +quit + + + stop the process + +V8: +https://v8.dev/docs/d8 +https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/d8.cc +https://riptutorial.com/v8/example/25393/useful-built-in-functions-and-objects-in-d8 + +SpiderMonkey: +https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Introduction_to_the_JavaScript_shell + +JavaScriptCore: +https://trac.webkit.org/wiki/JSC +https://github.com/WebKit/webkit/blob/master/Source/JavaScriptCore/jsc.cpp +*/ + +/** + * @type {number} + */ +let currentRealmIndex = Realm.current(); + +function resetRealm() { + if (currentRealmIndex !== 0) Realm.dispose(currentRealmIndex); + currentRealmIndex = Realm.createAllowCrossRealmAccess() +} + +/** + * @type {?Map} + */ +let globalState = null; + +function saveGlobalState() { + globalState = new Map(); + const currentGlobal = Realm.global(currentRealmIndex) + for (const k in currentGlobal) { + globalState.set(k, currentGlobal[k]); + } + + console.log(Array.from(globalState.entries())) +} + +function restoreGlobalState() { + if (globalState === null) throw Error("There is no saved state!") + + const currentGlobal = Realm.global(currentRealmIndex) + + console.log(Array.from(globalState.entries())) + + for (const k in currentGlobal) { + let prev = globalState.get(k); + if (prev !== currentGlobal[k]) { + currentGlobal[k] = prev; + } + } + globalState = null; +} + +// To prevent accessing to current global state +resetRealm(); + +// noinspection InfiniteLoopJS +while (true) { + let code = readline().replace(/\\n/g, '\n'); + + try { + switch (code) { + case "!reset": + resetRealm() + break; + case "!saveGlobalState": + saveGlobalState(); + break; + case "!restoreGlobalState": + restoreGlobalState(); + break; + default: + print(Realm.eval(currentRealmIndex, code)); + } + } catch(e) { + printErr(e.stack != null ? e.stack : e.toString()); + printErr('\nCODE:\n' + code); + } + + print(''); +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index bbbeae12a19..93581764ea0 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -32,8 +32,7 @@ import org.jetbrains.kotlin.js.config.* import org.jetbrains.kotlin.js.dce.DeadCodeElimination import org.jetbrains.kotlin.js.dce.InputFile import org.jetbrains.kotlin.js.dce.InputResource -import org.jetbrains.kotlin.js.engine.ScriptEngineNashorn -import org.jetbrains.kotlin.js.engine.ScriptEngineV8Lazy +import org.jetbrains.kotlin.js.engine.loadFiles import org.jetbrains.kotlin.js.facade.* import org.jetbrains.kotlin.js.parser.parse import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapError @@ -878,9 +877,9 @@ abstract class BasicBoxTest( runList += allJsFiles.map { filesToMinify[it]?.outputPath ?: it } val result = engineForMinifier.runAndRestoreContext { - runList.forEach(this::loadFile) + loadFiles(runList) overrideAsserter() - eval(SETUP_KOTLIN_OUTPUT) + eval(SETUP_KOTLIN_OUTPUT) runTestFunction(testModuleName, testPackage, testFunction, withModuleSystem) } TestCase.assertEquals(expectedResult, result) @@ -1047,9 +1046,7 @@ abstract class BasicBoxTest( private const val OLD_MODULE_SUFFIX = "-old" const val KOTLIN_TEST_INTERNAL = "\$kotlin_test_internal\$" - private val engineForMinifier = - if (runTestInNashorn) ScriptEngineNashorn() - else ScriptEngineV8Lazy(KotlinTestUtils.tmpDirForReusableFolder("j2v8_library_path").path) + private val engineForMinifier = createScriptEngine() const val overwriteReachableNodesProperty = "kotlin.js.overwriteReachableNodes" } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt index d5a8e06f490..54f3d77427e 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt @@ -1,22 +1,23 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.js.test +import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.js.engine.ScriptEngine import org.jetbrains.kotlin.js.engine.ScriptEngineNashorn import org.jetbrains.kotlin.js.engine.ScriptEngineV8 -import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.js.engine.loadFiles import org.junit.Assert fun createScriptEngine(): ScriptEngine { - return ScriptEngineNashorn() + return if (java.lang.Boolean.getBoolean("kotlin.js.useNashorn")) ScriptEngineNashorn() else ScriptEngineV8() } fun ScriptEngine.overrideAsserter() { - evalVoid("this['kotlin-test'].kotlin.test.overrideAsserter_wbnzx$(this['kotlin-test'].kotlin.test.DefaultAsserter);") + eval("this['kotlin-test'].kotlin.test.overrideAsserter_wbnzx$(this['kotlin-test'].kotlin.test.DefaultAsserter);") } fun ScriptEngine.runTestFunction( @@ -24,7 +25,7 @@ fun ScriptEngine.runTestFunction( testPackageName: String?, testFunctionName: String, withModuleSystem: Boolean -): String? { +): String { var script = when { withModuleSystem -> BasicBoxTest.KOTLIN_TEST_INTERNAL + ".require('" + testModuleName!! + "')" testModuleName === null -> "this" @@ -35,10 +36,9 @@ fun ScriptEngine.runTestFunction( script += ".$testPackageName" } - val testPackage = eval(script) - return callMethod(testPackage, testFunctionName).also { - releaseObject(testPackage) - } + script += ".$testFunctionName()" + + return eval(script) } abstract class AbstractJsTestChecker { @@ -51,7 +51,7 @@ abstract class AbstractJsTestChecker { withModuleSystem: Boolean ) { val actualResult = run(files, testModuleName, testPackageName, testFunctionName, withModuleSystem) - Assert.assertEquals(expectedResult, actualResult) + Assert.assertEquals(expectedResult, actualResult.normalize()) } private fun run( @@ -66,20 +66,28 @@ abstract class AbstractJsTestChecker { fun run(files: List) { - run(files) { null } + run(files) { "" } } - abstract fun checkStdout(files: List, expectedResult: String) + fun checkStdout(files: List, expectedResult: String) { + run(files) { + val actualResult = eval(GET_KOTLIN_OUTPUT) + Assert.assertEquals(expectedResult, actualResult.normalize()) + "" + } + } - protected abstract fun run(files: List, f: ScriptEngine.() -> Any?): Any? + private fun String.normalize() = StringUtil.convertLineSeparators(this) + + protected abstract fun run(files: List, f: ScriptEngine.() -> String): String } -fun ScriptEngine.runAndRestoreContext(f: ScriptEngine.() -> Any?): Any? { +fun ScriptEngine.runAndRestoreContext(f: ScriptEngine.() -> String): String { return try { - saveState() + saveGlobalState() f() } finally { - restoreState() + restoreGlobalState() } } @@ -96,7 +104,7 @@ abstract class AbstractNashornJsTestChecker : AbstractJsTestChecker() { protected open fun beforeRun() {} - override fun run(files: List, f: ScriptEngine.() -> Any?): Any? { + override fun run(files: List, f: ScriptEngine.() -> String): String { // Recreate the engine once in a while if (engineUsageCnt++ > 100) { engineUsageCnt = 0 @@ -106,23 +114,17 @@ abstract class AbstractNashornJsTestChecker : AbstractJsTestChecker() { beforeRun() return engine.runAndRestoreContext { - files.forEach { loadFile(it) } + loadFiles(files) f() } } - override fun checkStdout(files: List, expectedResult: String) { - run(files) - val actualResult = engine.eval(GET_KOTLIN_OUTPUT) - Assert.assertEquals(expectedResult, actualResult) - } - protected abstract val preloadedScripts: List protected open fun createScriptEngineForTest(): ScriptEngineNashorn { val engine = ScriptEngineNashorn() - preloadedScripts.forEach { engine.loadFile(it) } + engine.loadFiles(preloadedScripts) return engine } @@ -134,7 +136,7 @@ const val GET_KOTLIN_OUTPUT = "kotlin.kotlin.io.output.buffer;" object NashornJsTestChecker : AbstractNashornJsTestChecker() { override fun beforeRun() { - engine.evalVoid(SETUP_KOTLIN_OUTPUT) + engine.eval(SETUP_KOTLIN_OUTPUT) } override val preloadedScripts = listOf( @@ -159,69 +161,50 @@ class NashornIrJsTestChecker : AbstractNashornJsTestChecker() { ) } -abstract class AbstractV8JsTestChecker : AbstractJsTestChecker() { - protected abstract val engine: ScriptEngineV8 +object V8JsTestChecker : AbstractJsTestChecker() { + private val engineTL = object : ThreadLocal() { + override fun initialValue() = + ScriptEngineV8().apply { + val preloadedScripts = listOf( + BasicBoxTest.DIST_DIR_JS_PATH + "kotlin.js", + BasicBoxTest.DIST_DIR_JS_PATH + "kotlin-test.js" + ) + loadFiles(preloadedScripts) - override fun checkStdout(files: List, expectedResult: String) { - run(files) { - val actualResult = engine.eval(GET_KOTLIN_OUTPUT) - Assert.assertEquals(expectedResult, actualResult) - } - } -} + overrideAsserter() + } -object V8JsTestChecker : AbstractV8JsTestChecker() { - override val engine get() = tlsEngine.get() - - private val tlsEngine = object : ThreadLocal() { - override fun initialValue() = createV8Engine() override fun remove() { get().release() } } - private fun createV8Engine(): ScriptEngineV8 { - val v8 = ScriptEngineV8(KotlinTestUtils.tmpDirForReusableFolder("j2v8_library_path").path) + private val engine get() = engineTL.get() - listOf( - BasicBoxTest.DIST_DIR_JS_PATH + "kotlin.js", - BasicBoxTest.DIST_DIR_JS_PATH + "kotlin-test.js" - ).forEach { v8.loadFile(it) } - - v8.overrideAsserter() - - return v8 - } - - override fun run(files: List, f: ScriptEngine.() -> Any?): Any? { - engine.evalVoid(SETUP_KOTLIN_OUTPUT) + override fun run(files: List, f: ScriptEngine.() -> String): String { + engine.eval(SETUP_KOTLIN_OUTPUT) return engine.runAndRestoreContext { - files.forEach { loadFile(it) } + loadFiles(files) f() } } } -object V8IrJsTestChecker : AbstractV8JsTestChecker() { - override val engine get() = ScriptEngineV8(KotlinTestUtils.tmpDirForReusableFolder("j2v8_library_path").path) - - override fun run(files: List, f: ScriptEngine.() -> Any?): Any? { - - val v8 = engine - - val v = try { - files.forEach { v8.loadFile(it) } - v8.f() - } catch (t: Throwable) { - try { - v8.release() - } finally { - // Don't mask the original exception - throw t - } +object V8IrJsTestChecker : AbstractJsTestChecker() { + private val engineTL = object : ThreadLocal() { + override fun initialValue() = ScriptEngineV8() + override fun remove() { + get().release() } - v8.release() - - return v } -} \ No newline at end of file + + override fun run(files: List, f: ScriptEngine.() -> String): String { + val engine = engineTL.get() + return try { + engine.loadFiles(files) + engine.f() + } finally { + engine.reset() + } + } +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/BasicOptimizerTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/BasicOptimizerTest.kt index 900de61b370..90429adbfe6 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/BasicOptimizerTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/BasicOptimizerTest.kt @@ -1,17 +1,6 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the 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. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.js.test.optimizer @@ -128,8 +117,8 @@ abstract class BasicOptimizerTest(private var basePath: String) { private fun runScript(fileName: String, code: String) { val engine = createScriptEngine() - engine.evalVoid(code) - val result = engine.eval("box()") + engine.eval(code) + val result = engine.eval("box()") Assert.assertEquals("$fileName: box() function must return 'OK'", "OK", result) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/AbstractWebDemoExamplesTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/AbstractWebDemoExamplesTest.kt index d6cd283d091..0c42c7c4567 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/AbstractWebDemoExamplesTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/AbstractWebDemoExamplesTest.kt @@ -1,15 +1,14 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.js.test.semantics -import com.eclipsesource.v8.V8ScriptException import com.google.common.collect.Lists import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.js.facade.MainCallParameters -import org.jetbrains.kotlin.js.test.* +import org.jetbrains.kotlin.js.test.BasicBoxTest import java.io.File import javax.script.ScriptException @@ -29,7 +28,7 @@ abstract class AbstractWebDemoExamplesTest(relativePath: String) : BasicBoxTest( testChecker.checkStdout(jsFiles, expectedResult) } - @Throws(ScriptException::class, V8ScriptException::class) + @Throws(ScriptException::class) protected fun runMainAndCheckOutput(fileName: String, expectedResult: String, vararg args: String) { doTest(pathToTestDir + fileName, expectedResult, MainCallParameters.mainWithArguments(Lists.newArrayList(*args))) } @@ -38,4 +37,4 @@ abstract class AbstractWebDemoExamplesTest(relativePath: String) : BasicBoxTest( val expectedResult = StringUtil.convertLineSeparators(File(pathToTestDir + testName + testId + ".out").readText()) doTest(pathToTestDir + testName + ".kt", expectedResult, MainCallParameters.mainWithArguments(Lists.newArrayList(*args))) } -} \ No newline at end of file +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/MultiModuleOrderTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/MultiModuleOrderTest.kt index 6cdcb0ff9af..01576d5c1b5 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/MultiModuleOrderTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/MultiModuleOrderTest.kt @@ -1,22 +1,10 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the 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. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.js.test.semantics -import com.eclipsesource.v8.V8ScriptException import org.jetbrains.kotlin.js.test.BasicBoxTest import java.io.File import javax.script.ScriptException @@ -47,7 +35,7 @@ class MultiModuleOrderTest : BasicBoxTest(pathToTestGroupDir, testGroupDir) { testChecker.run(listOf(mainJsFile, libJsFile)) } catch (e: RuntimeException) { - assertTrue(e is ScriptException || e is V8ScriptException) + assertTrue(e is ScriptException || e is IllegalStateException) val message = e.message!! assertTrue("Exception message should contain reference to dependency (lib)", "'lib'" in message) assertTrue("Exception message should contain reference to module that failed to load (main)", "'main'" in message) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/WebDemoCanvasExamplesTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/WebDemoCanvasExamplesTest.java index 7ab7b229db8..f3e48107062 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/WebDemoCanvasExamplesTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/WebDemoCanvasExamplesTest.java @@ -1,22 +1,10 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the 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. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.js.test.semantics; -import com.eclipsesource.v8.V8ScriptException; import org.jetbrains.annotations.NotNull; import javax.script.ScriptException; @@ -55,16 +43,16 @@ public final class WebDemoCanvasExamplesTest extends AbstractWebDemoExamplesTest catch (ScriptException e) { verifyException("\"" + firstUnknownSymbolEncountered + "\"", e.getMessage()); } - catch (V8ScriptException e) { - verifyException(firstUnknownSymbolEncountered, e.getJSMessage()); + catch (IllegalStateException e) { + verifyException(firstUnknownSymbolEncountered, e.getMessage()); } } private static void verifyException(@NotNull String firstUnknownSymbolEncountered, @NotNull String message) { String expectedErrorMessage = "ReferenceError: " + firstUnknownSymbolEncountered + " is not defined"; assertTrue("Unexpected error when running compiled canvas examples with rhino.\n" + - "Expected: " + expectedErrorMessage + "\n" + - "Actual: " + message, - message.startsWith(expectedErrorMessage)); + "Expected message contains \"" + expectedErrorMessage + "\".\n" + + "Message: " + message, + message.contains(expectedErrorMessage)); } } diff --git a/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsReplEvaluator.kt b/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsReplEvaluator.kt index 0d221f19403..4e30e0dcb14 100644 --- a/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsReplEvaluator.kt +++ b/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsReplEvaluator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -21,7 +21,7 @@ class JsReplEvaluator : ReplEvaluator { ): ReplEvalResult { return try { val evaluationState = state.asState(JsEvaluationState::class.java) - val evalResult = evaluationState.engine.eval(compileResult.data as String) + val evalResult = evaluationState.engine.evalWithTypedResult(compileResult.data as String) ReplEvalResult.ValueResult("result", evalResult, "Any?") } catch (e: Exception) { ReplEvalResult.Error.Runtime("Error while evaluating", e) diff --git a/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsReplUtils.kt b/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsReplUtils.kt index d0c35af5172..6fe2772dbd3 100644 --- a/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsReplUtils.kt +++ b/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsReplUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -9,7 +9,7 @@ import org.jetbrains.kotlin.cli.common.repl.* import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.ir.backend.js.utils.NameTables -import org.jetbrains.kotlin.js.engine.ScriptEngine +import org.jetbrains.kotlin.js.engine.ScriptEngineWithTypedResult import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.reflect.KClass import kotlin.script.experimental.api.CompiledScript @@ -31,9 +31,9 @@ abstract class JsCompilationState( val nameTables: NameTables, val dependencies: List) : JsState(lock) -class JsEvaluationState(lock: ReentrantReadWriteLock, val engine: ScriptEngine) : JsState(lock) { +class JsEvaluationState(lock: ReentrantReadWriteLock, val engine: ScriptEngineWithTypedResult) : JsState(lock) { override fun dispose() { - engine.release() + engine.reset() } } diff --git a/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsScriptEvaluator.kt b/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsScriptEvaluator.kt index 28479d88a91..34375780fa8 100644 --- a/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsScriptEvaluator.kt +++ b/libraries/scripting/js/src/org/jetbrains/kotlin/scripting/repl/js/JsScriptEvaluator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -17,7 +17,7 @@ class JsScriptEvaluator : ScriptEvaluator { scriptEvaluationConfiguration: ScriptEvaluationConfiguration ): ResultWithDiagnostics { return try { - val evalResult = engine.eval((compiledScript as JsCompiledScript).jsCode) + val evalResult = engine.evalWithTypedResult((compiledScript as JsCompiledScript).jsCode) ResultWithDiagnostics.Success( EvaluationResult( ResultValue.Value(