[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.
This commit is contained in:
Zalim Bashorov
2020-12-04 01:59:25 +03:00
parent 39cc149da0
commit 4c69f78de8
15 changed files with 357 additions and 265 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ dependencies {
compile(project(":compiler:util")) compile(project(":compiler:util"))
compile(project(":js:js.ast")) compile(project(":js:js.ast"))
compile(project(":js:js.translator")) compile(project(":js:js.translator"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") } compile(intellijCoreDep()) { includeJars("intellij-core") }
} }
sourceSets { sourceSets {
@@ -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 = "<END>$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
}
}
}
@@ -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. * 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 package org.jetbrains.kotlin.js.engine
interface ScriptEngine { interface ScriptEngine {
fun <T> eval(script: String): T fun eval(script: String): String
fun evalVoid(script: String)
fun <T> callMethod(obj: Any, name: String, vararg args: Any?): T
fun loadFile(path: String)
fun release()
fun <T> releaseObject(t: T)
fun saveState() // TODO Add API to load few files at once?
fun restoreState() 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 <R> evalWithTypedResult(script: String): R
}
fun ScriptEngine.loadFiles(files: List<String>) {
files.forEach { loadFile(it) }
} }
@@ -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. * 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") @file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
package org.jetbrains.kotlin.js.engine package org.jetbrains.kotlin.js.engine
import jdk.nashorn.api.scripting.NashornScriptEngine
import jdk.nashorn.api.scripting.NashornScriptEngineFactory import jdk.nashorn.api.scripting.NashornScriptEngineFactory
import jdk.nashorn.internal.runtime.ScriptRuntime import jdk.nashorn.internal.runtime.ScriptRuntime
import javax.script.Invocable
class ScriptEngineNashorn : ScriptEngine { class ScriptEngineNashorn : ScriptEngineWithTypedResult {
private var savedState: Map<String, Any?>? = null private var savedState: Map<String, Any?>? = null
// TODO use "-strict" // TODO use "-strict"
private val myEngine = NashornScriptEngineFactory().getScriptEngine("--language=es5", "--no-java", "--no-syntax-extensions") private val myEngine = NashornScriptEngineFactory().getScriptEngine("--language=es5", "--no-java", "--no-syntax-extensions")
@Suppress("UNCHECKED_CAST") override fun eval(script: String): String = evalWithTypedResult<Any?>(script).toString()
override fun <T> eval(script: String): T {
return myEngine.eval(script) as T
}
override fun evalVoid(script: String) {
myEngine.eval(script)
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T> callMethod(obj: Any, name: String, vararg args: Any?): T { override fun <R> evalWithTypedResult(script: String): R {
return (myEngine as Invocable).invokeMethod(obj, name, *args) as T return myEngine.eval(script) as R
} }
override fun loadFile(path: String) { override fun loadFile(path: String) {
evalVoid("load('${path.replace('\\', '/')}');") eval("load('${path.replace('\\', '/')}');")
} }
override fun release() {} override fun reset() {
override fun <T> releaseObject(t: T) {} throw UnsupportedOperationException()
}
private fun getGlobalState(): MutableMap<String, Any?> = evalWithTypedResult("this")
private fun getGlobalState(): MutableMap<String, Any?> = eval("this") override fun saveGlobalState() {
override fun saveState() {
savedState = getGlobalState().toMap() savedState = getGlobalState().toMap()
} }
override fun restoreState() { override fun restoreGlobalState() {
val globalState = getGlobalState() val globalState = getGlobalState()
val originalState = savedState!! val originalState = savedState!!
for (key in globalState.keys) { for (key in globalState.keys) {
globalState[key] = originalState[key] ?: ScriptRuntime.UNDEFINED globalState[key] = originalState[key] ?: ScriptRuntime.UNDEFINED
} }
} }
override fun release() {
}
} }
@@ -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. * 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 package org.jetbrains.kotlin.js.engine
import com.eclipsesource.v8.V8 class ScriptEngineV8 : ProcessBasedScriptEngine(System.getProperty("javascript.engine.path.V8"))
import com.eclipsesource.v8.V8Array
import com.eclipsesource.v8.V8Object
import com.eclipsesource.v8.utils.V8ObjectUtils
import java.io.File
class ScriptEngineV8(LIBRARY_PATH_BASE: String) : ScriptEngine { fun main() {
// System.setProperty("javascript.engine.path.V8", "<path-to-d8>")
override fun <T> releaseObject(t: T) { val vm = ScriptEngineV8()
(t as? V8Object)?.release() println("Welcome!")
} while (true) {
print("> ")
private var savedState: List<String>? = null val t = readLine()
try {
override fun restoreState() { println(vm.eval(t!!))
val scriptBuilder = StringBuilder() } catch (e: Throwable) {
System.err.println(e)
val globalState = getGlobalPropertyNames()
val originalState = savedState!!
for (key in globalState) {
if (key !in originalState) {
scriptBuilder.append("this['$key'] = void 0;\n")
}
} }
evalVoid(scriptBuilder.toString())
}
private fun getGlobalPropertyNames(): List<String> {
val v8Array = eval<V8Array>("Object.getOwnPropertyNames(this)")
@Suppress("UNCHECKED_CAST") val javaArray = V8ObjectUtils.toList(v8Array) as List<String>
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 <T> eval(script: String): T {
return myRuntime.executeScript(script) as T
}
override fun evalVoid(script: String) {
return myRuntime.executeVoidScript(script)
}
@Suppress("UNCHECKED_CAST")
override fun <T> 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 <T> eval(script: String) = engine.eval<T>(script)
override fun saveState() = engine.saveState()
override fun evalVoid(script: String) = engine.evalVoid(script)
override fun <T> callMethod(obj: Any, name: String, vararg args: Any?) = engine.callMethod<T>(obj, name, args)
override fun loadFile(path: String) = engine.loadFile(path)
override fun release() = engine.release()
override fun <T> releaseObject(t: T) = engine.releaseObject(t)
override fun restoreState() = engine.restoreState()
private val engine by lazy { ScriptEngineV8(LIBRARY_PATH_BASE) }
}
@@ -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.
*/
// <path-to-v8> 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<string, ?any>}
*/
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('<END>');
}
@@ -32,8 +32,7 @@ import org.jetbrains.kotlin.js.config.*
import org.jetbrains.kotlin.js.dce.DeadCodeElimination import org.jetbrains.kotlin.js.dce.DeadCodeElimination
import org.jetbrains.kotlin.js.dce.InputFile import org.jetbrains.kotlin.js.dce.InputFile
import org.jetbrains.kotlin.js.dce.InputResource import org.jetbrains.kotlin.js.dce.InputResource
import org.jetbrains.kotlin.js.engine.ScriptEngineNashorn import org.jetbrains.kotlin.js.engine.loadFiles
import org.jetbrains.kotlin.js.engine.ScriptEngineV8Lazy
import org.jetbrains.kotlin.js.facade.* import org.jetbrains.kotlin.js.facade.*
import org.jetbrains.kotlin.js.parser.parse import org.jetbrains.kotlin.js.parser.parse
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapError import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapError
@@ -878,9 +877,9 @@ abstract class BasicBoxTest(
runList += allJsFiles.map { filesToMinify[it]?.outputPath ?: it } runList += allJsFiles.map { filesToMinify[it]?.outputPath ?: it }
val result = engineForMinifier.runAndRestoreContext { val result = engineForMinifier.runAndRestoreContext {
runList.forEach(this::loadFile) loadFiles(runList)
overrideAsserter() overrideAsserter()
eval<String>(SETUP_KOTLIN_OUTPUT) eval(SETUP_KOTLIN_OUTPUT)
runTestFunction(testModuleName, testPackage, testFunction, withModuleSystem) runTestFunction(testModuleName, testPackage, testFunction, withModuleSystem)
} }
TestCase.assertEquals(expectedResult, result) TestCase.assertEquals(expectedResult, result)
@@ -1047,9 +1046,7 @@ abstract class BasicBoxTest(
private const val OLD_MODULE_SUFFIX = "-old" private const val OLD_MODULE_SUFFIX = "-old"
const val KOTLIN_TEST_INTERNAL = "\$kotlin_test_internal\$" const val KOTLIN_TEST_INTERNAL = "\$kotlin_test_internal\$"
private val engineForMinifier = private val engineForMinifier = createScriptEngine()
if (runTestInNashorn) ScriptEngineNashorn()
else ScriptEngineV8Lazy(KotlinTestUtils.tmpDirForReusableFolder("j2v8_library_path").path)
const val overwriteReachableNodesProperty = "kotlin.js.overwriteReachableNodes" const val overwriteReachableNodesProperty = "kotlin.js.overwriteReachableNodes"
} }
@@ -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. * 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 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.ScriptEngine
import org.jetbrains.kotlin.js.engine.ScriptEngineNashorn import org.jetbrains.kotlin.js.engine.ScriptEngineNashorn
import org.jetbrains.kotlin.js.engine.ScriptEngineV8 import org.jetbrains.kotlin.js.engine.ScriptEngineV8
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.js.engine.loadFiles
import org.junit.Assert import org.junit.Assert
fun createScriptEngine(): ScriptEngine { fun createScriptEngine(): ScriptEngine {
return ScriptEngineNashorn() return if (java.lang.Boolean.getBoolean("kotlin.js.useNashorn")) ScriptEngineNashorn() else ScriptEngineV8()
} }
fun ScriptEngine.overrideAsserter() { 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( fun ScriptEngine.runTestFunction(
@@ -24,7 +25,7 @@ fun ScriptEngine.runTestFunction(
testPackageName: String?, testPackageName: String?,
testFunctionName: String, testFunctionName: String,
withModuleSystem: Boolean withModuleSystem: Boolean
): String? { ): String {
var script = when { var script = when {
withModuleSystem -> BasicBoxTest.KOTLIN_TEST_INTERNAL + ".require('" + testModuleName!! + "')" withModuleSystem -> BasicBoxTest.KOTLIN_TEST_INTERNAL + ".require('" + testModuleName!! + "')"
testModuleName === null -> "this" testModuleName === null -> "this"
@@ -35,10 +36,9 @@ fun ScriptEngine.runTestFunction(
script += ".$testPackageName" script += ".$testPackageName"
} }
val testPackage = eval<Any>(script) script += ".$testFunctionName()"
return callMethod<String?>(testPackage, testFunctionName).also {
releaseObject(testPackage) return eval(script)
}
} }
abstract class AbstractJsTestChecker { abstract class AbstractJsTestChecker {
@@ -51,7 +51,7 @@ abstract class AbstractJsTestChecker {
withModuleSystem: Boolean withModuleSystem: Boolean
) { ) {
val actualResult = run(files, testModuleName, testPackageName, testFunctionName, withModuleSystem) val actualResult = run(files, testModuleName, testPackageName, testFunctionName, withModuleSystem)
Assert.assertEquals(expectedResult, actualResult) Assert.assertEquals(expectedResult, actualResult.normalize())
} }
private fun run( private fun run(
@@ -66,20 +66,28 @@ abstract class AbstractJsTestChecker {
fun run(files: List<String>) { fun run(files: List<String>) {
run(files) { null } run(files) { "" }
} }
abstract fun checkStdout(files: List<String>, expectedResult: String) fun checkStdout(files: List<String>, expectedResult: String) {
run(files) {
val actualResult = eval(GET_KOTLIN_OUTPUT)
Assert.assertEquals(expectedResult, actualResult.normalize())
""
}
}
protected abstract fun run(files: List<String>, f: ScriptEngine.() -> Any?): Any? private fun String.normalize() = StringUtil.convertLineSeparators(this)
protected abstract fun run(files: List<String>, f: ScriptEngine.() -> String): String
} }
fun ScriptEngine.runAndRestoreContext(f: ScriptEngine.() -> Any?): Any? { fun ScriptEngine.runAndRestoreContext(f: ScriptEngine.() -> String): String {
return try { return try {
saveState() saveGlobalState()
f() f()
} finally { } finally {
restoreState() restoreGlobalState()
} }
} }
@@ -96,7 +104,7 @@ abstract class AbstractNashornJsTestChecker : AbstractJsTestChecker() {
protected open fun beforeRun() {} protected open fun beforeRun() {}
override fun run(files: List<String>, f: ScriptEngine.() -> Any?): Any? { override fun run(files: List<String>, f: ScriptEngine.() -> String): String {
// Recreate the engine once in a while // Recreate the engine once in a while
if (engineUsageCnt++ > 100) { if (engineUsageCnt++ > 100) {
engineUsageCnt = 0 engineUsageCnt = 0
@@ -106,23 +114,17 @@ abstract class AbstractNashornJsTestChecker : AbstractJsTestChecker() {
beforeRun() beforeRun()
return engine.runAndRestoreContext { return engine.runAndRestoreContext {
files.forEach { loadFile(it) } loadFiles(files)
f() f()
} }
} }
override fun checkStdout(files: List<String>, expectedResult: String) {
run(files)
val actualResult = engine.eval<String>(GET_KOTLIN_OUTPUT)
Assert.assertEquals(expectedResult, actualResult)
}
protected abstract val preloadedScripts: List<String> protected abstract val preloadedScripts: List<String>
protected open fun createScriptEngineForTest(): ScriptEngineNashorn { protected open fun createScriptEngineForTest(): ScriptEngineNashorn {
val engine = ScriptEngineNashorn() val engine = ScriptEngineNashorn()
preloadedScripts.forEach { engine.loadFile(it) } engine.loadFiles(preloadedScripts)
return engine return engine
} }
@@ -134,7 +136,7 @@ const val GET_KOTLIN_OUTPUT = "kotlin.kotlin.io.output.buffer;"
object NashornJsTestChecker : AbstractNashornJsTestChecker() { object NashornJsTestChecker : AbstractNashornJsTestChecker() {
override fun beforeRun() { override fun beforeRun() {
engine.evalVoid(SETUP_KOTLIN_OUTPUT) engine.eval(SETUP_KOTLIN_OUTPUT)
} }
override val preloadedScripts = listOf( override val preloadedScripts = listOf(
@@ -159,69 +161,50 @@ class NashornIrJsTestChecker : AbstractNashornJsTestChecker() {
) )
} }
abstract class AbstractV8JsTestChecker : AbstractJsTestChecker() { object V8JsTestChecker : AbstractJsTestChecker() {
protected abstract val engine: ScriptEngineV8 private val engineTL = object : ThreadLocal<ScriptEngineV8>() {
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<String>, expectedResult: String) { overrideAsserter()
run(files) { }
val actualResult = engine.eval<String>(GET_KOTLIN_OUTPUT)
Assert.assertEquals(expectedResult, actualResult)
}
}
}
object V8JsTestChecker : AbstractV8JsTestChecker() {
override val engine get() = tlsEngine.get()
private val tlsEngine = object : ThreadLocal<ScriptEngineV8>() {
override fun initialValue() = createV8Engine()
override fun remove() { override fun remove() {
get().release() get().release()
} }
} }
private fun createV8Engine(): ScriptEngineV8 { private val engine get() = engineTL.get()
val v8 = ScriptEngineV8(KotlinTestUtils.tmpDirForReusableFolder("j2v8_library_path").path)
listOf( override fun run(files: List<String>, f: ScriptEngine.() -> String): String {
BasicBoxTest.DIST_DIR_JS_PATH + "kotlin.js", engine.eval(SETUP_KOTLIN_OUTPUT)
BasicBoxTest.DIST_DIR_JS_PATH + "kotlin-test.js"
).forEach { v8.loadFile(it) }
v8.overrideAsserter()
return v8
}
override fun run(files: List<String>, f: ScriptEngine.() -> Any?): Any? {
engine.evalVoid(SETUP_KOTLIN_OUTPUT)
return engine.runAndRestoreContext { return engine.runAndRestoreContext {
files.forEach { loadFile(it) } loadFiles(files)
f() f()
} }
} }
} }
object V8IrJsTestChecker : AbstractV8JsTestChecker() { object V8IrJsTestChecker : AbstractJsTestChecker() {
override val engine get() = ScriptEngineV8(KotlinTestUtils.tmpDirForReusableFolder("j2v8_library_path").path) private val engineTL = object : ThreadLocal<ScriptEngineV8>() {
override fun initialValue() = ScriptEngineV8()
override fun run(files: List<String>, f: ScriptEngine.() -> Any?): Any? { override fun remove() {
get().release()
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
}
} }
v8.release() }
return v override fun run(files: List<String>, f: ScriptEngine.() -> String): String {
val engine = engineTL.get()
return try {
engine.loadFiles(files)
engine.f()
} finally {
engine.reset()
}
} }
} }
@@ -1,17 +1,6 @@
/* /*
* Copyright 2010-2016 JetBrains s.r.o. * 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.
* 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.
*/ */
package org.jetbrains.kotlin.js.test.optimizer 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) { private fun runScript(fileName: String, code: String) {
val engine = createScriptEngine() val engine = createScriptEngine()
engine.evalVoid(code) engine.eval(code)
val result = engine.eval<String>("box()") val result = engine.eval("box()")
Assert.assertEquals("$fileName: box() function must return 'OK'", "OK", result) Assert.assertEquals("$fileName: box() function must return 'OK'", "OK", result)
} }
@@ -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. * 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 package org.jetbrains.kotlin.js.test.semantics
import com.eclipsesource.v8.V8ScriptException
import com.google.common.collect.Lists import com.google.common.collect.Lists
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.js.facade.MainCallParameters 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 java.io.File
import javax.script.ScriptException import javax.script.ScriptException
@@ -29,7 +28,7 @@ abstract class AbstractWebDemoExamplesTest(relativePath: String) : BasicBoxTest(
testChecker.checkStdout(jsFiles, expectedResult) testChecker.checkStdout(jsFiles, expectedResult)
} }
@Throws(ScriptException::class, V8ScriptException::class) @Throws(ScriptException::class)
protected fun runMainAndCheckOutput(fileName: String, expectedResult: String, vararg args: String) { protected fun runMainAndCheckOutput(fileName: String, expectedResult: String, vararg args: String) {
doTest(pathToTestDir + fileName, expectedResult, MainCallParameters.mainWithArguments(Lists.newArrayList(*args))) doTest(pathToTestDir + fileName, expectedResult, MainCallParameters.mainWithArguments(Lists.newArrayList(*args)))
} }
@@ -1,22 +1,10 @@
/* /*
* Copyright 2010-2016 JetBrains s.r.o. * 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.
* 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.
*/ */
package org.jetbrains.kotlin.js.test.semantics package org.jetbrains.kotlin.js.test.semantics
import com.eclipsesource.v8.V8ScriptException
import org.jetbrains.kotlin.js.test.BasicBoxTest import org.jetbrains.kotlin.js.test.BasicBoxTest
import java.io.File import java.io.File
import javax.script.ScriptException import javax.script.ScriptException
@@ -47,7 +35,7 @@ class MultiModuleOrderTest : BasicBoxTest(pathToTestGroupDir, testGroupDir) {
testChecker.run(listOf(mainJsFile, libJsFile)) testChecker.run(listOf(mainJsFile, libJsFile))
} }
catch (e: RuntimeException) { catch (e: RuntimeException) {
assertTrue(e is ScriptException || e is V8ScriptException) assertTrue(e is ScriptException || e is IllegalStateException)
val message = e.message!! val message = e.message!!
assertTrue("Exception message should contain reference to dependency (lib)", "'lib'" in 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) assertTrue("Exception message should contain reference to module that failed to load (main)", "'main'" in message)
@@ -1,22 +1,10 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * 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.
* 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.
*/ */
package org.jetbrains.kotlin.js.test.semantics; package org.jetbrains.kotlin.js.test.semantics;
import com.eclipsesource.v8.V8ScriptException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.script.ScriptException; import javax.script.ScriptException;
@@ -55,16 +43,16 @@ public final class WebDemoCanvasExamplesTest extends AbstractWebDemoExamplesTest
catch (ScriptException e) { catch (ScriptException e) {
verifyException("\"" + firstUnknownSymbolEncountered + "\"", e.getMessage()); verifyException("\"" + firstUnknownSymbolEncountered + "\"", e.getMessage());
} }
catch (V8ScriptException e) { catch (IllegalStateException e) {
verifyException(firstUnknownSymbolEncountered, e.getJSMessage()); verifyException(firstUnknownSymbolEncountered, e.getMessage());
} }
} }
private static void verifyException(@NotNull String firstUnknownSymbolEncountered, @NotNull String message) { private static void verifyException(@NotNull String firstUnknownSymbolEncountered, @NotNull String message) {
String expectedErrorMessage = "ReferenceError: " + firstUnknownSymbolEncountered + " is not defined"; String expectedErrorMessage = "ReferenceError: " + firstUnknownSymbolEncountered + " is not defined";
assertTrue("Unexpected error when running compiled canvas examples with rhino.\n" + assertTrue("Unexpected error when running compiled canvas examples with rhino.\n" +
"Expected: " + expectedErrorMessage + "\n" + "Expected message contains \"" + expectedErrorMessage + "\".\n" +
"Actual: " + message, "Message: " + message,
message.startsWith(expectedErrorMessage)); message.contains(expectedErrorMessage));
} }
} }
@@ -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. * 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 { ): ReplEvalResult {
return try { return try {
val evaluationState = state.asState(JsEvaluationState::class.java) val evaluationState = state.asState(JsEvaluationState::class.java)
val evalResult = evaluationState.engine.eval<Any?>(compileResult.data as String) val evalResult = evaluationState.engine.evalWithTypedResult<Any?>(compileResult.data as String)
ReplEvalResult.ValueResult("result", evalResult, "Any?") ReplEvalResult.ValueResult("result", evalResult, "Any?")
} catch (e: Exception) { } catch (e: Exception) {
ReplEvalResult.Error.Runtime("Error while evaluating", e) ReplEvalResult.Error.Runtime("Error while evaluating", e)
@@ -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. * 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.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables 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 java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.script.experimental.api.CompiledScript import kotlin.script.experimental.api.CompiledScript
@@ -31,9 +31,9 @@ abstract class JsCompilationState(
val nameTables: NameTables, val nameTables: NameTables,
val dependencies: List<ModuleDescriptor>) : JsState(lock) val dependencies: List<ModuleDescriptor>) : JsState(lock)
class JsEvaluationState(lock: ReentrantReadWriteLock, val engine: ScriptEngine) : JsState(lock) { class JsEvaluationState(lock: ReentrantReadWriteLock, val engine: ScriptEngineWithTypedResult) : JsState(lock) {
override fun dispose() { override fun dispose() {
engine.release() engine.reset()
} }
} }
@@ -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. * 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 scriptEvaluationConfiguration: ScriptEvaluationConfiguration
): ResultWithDiagnostics<EvaluationResult> { ): ResultWithDiagnostics<EvaluationResult> {
return try { return try {
val evalResult = engine.eval<Any?>((compiledScript as JsCompiledScript).jsCode) val evalResult = engine.evalWithTypedResult<Any?>((compiledScript as JsCompiledScript).jsCode)
ResultWithDiagnostics.Success( ResultWithDiagnostics.Success(
EvaluationResult( EvaluationResult(
ResultValue.Value( ResultValue.Value(