Prepare repl interfaces and generic implementation for separation into possibly remote parts

This commit is contained in:
Ilya Chernikov
2016-09-14 21:42:58 +02:00
parent f992f91686
commit eaa332019a
8 changed files with 312 additions and 116 deletions
+1
View File
@@ -11,5 +11,6 @@
<orderEntry type="library" scope="PROVIDED" name="intellij-core" level="project" />
<orderEntry type="library" exported="" name="cli-parser" level="project" />
<orderEntry type="library" name="jps" level="project" />
<orderEntry type="module" module-name="descriptor.loader.java" />
</component>
</module>
@@ -0,0 +1,84 @@
/*
* 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.
*/
package org.jetbrains.kotlin.cli.common.repl
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.io.File
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>) : ReplCompiledEvaluator {
private val classpath = baseClasspath.toMutableList()
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader =
org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader))
private val classLoaderLock = ReentrantReadWriteLock()
private class ClassWithInstance(val klass: Class<*>, val instance: Any)
private val compiledLoadedClassesHistory = arrayListOf<Pair<ReplCodeLine, ClassWithInstance>>()
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult {
checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let {
return@eval ReplEvalResult.HistoryMismatch(it)
}
classLoaderLock.read {
compiledClasses.filter { it.path.endsWith(".class") }
.forEach {
classLoader.addClass(JvmClassName.byInternalName(it.path.replaceFirst("\\.class$".toRegex(), "")), it.bytes)
}
}
val scriptClass = classLoaderLock.read { classLoader.loadClass("Line${codeLine.no}") }
val constructorParams = compiledLoadedClassesHistory.map { it.second.klass }.toTypedArray()
val constructorArgs = compiledLoadedClassesHistory.map { it.second.instance }.toTypedArray()
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
val scriptInstance =
try {
evalWithIO { scriptInstanceConstructor.newInstance(*constructorArgs) }
}
catch (e: Throwable) {
// ignore everything in the stack trace until this constructor call
return ReplEvalResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"))
}
compiledLoadedClassesHistory.add(codeLine to ClassWithInstance(scriptClass, scriptInstance))
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val rv: Any? = rvField.get(scriptInstance)
return if (hasResult) ReplEvalResult.ValueResult(rv) else ReplEvalResult.UnitResult
}
fun dependenciesAdded(newClasspath: List<File>) {
classLoaderLock.write {
classpath.addAll(newClasspath)
classLoader = org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(newClasspath.map { it.toURI().toURL() }.toTypedArray(), classLoader))
}
}
companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
}
}
@@ -0,0 +1,82 @@
/*
* 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.
*/
package org.jetbrains.kotlin.cli.common.repl
import java.io.File
import java.io.Serializable
import java.util.*
data class ReplCodeLine(val no: Int, val code: String)
data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable {
override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false
override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes)
}
sealed class ReplCheckResult : Serializable {
object Ok : ReplCheckResult()
object Incomplete : ReplCheckResult()
class Error(val message: String) : ReplCheckResult()
}
sealed class ReplCompileResult : Serializable {
class CompiledClasses(val classes: List<CompiledClassData>, val hasResult: Boolean) : ReplCompileResult()
object Incomplete : ReplCompileResult()
class HistoryMismatch(val lineNo: Int): ReplCompileResult()
class Error(val message: String) : ReplCompileResult()
}
sealed class ReplEvalResult : Serializable {
class ValueResult(val value: Any?) : ReplEvalResult()
object UnitResult : ReplEvalResult()
object Incomplete : ReplEvalResult()
class HistoryMismatch(val lineNo: Int): ReplEvalResult()
sealed class Error(val message: String) : ReplEvalResult() {
class Runtime(message: String) : Error(message)
class CompileTime(message: String) : Error(message)
}
}
interface ReplChecker {
fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult
}
interface ReplCompiler : ReplChecker {
fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult
// a callback
fun dependenciesAdded(classpath: List<File>)
}
interface ReplCompiledEvaluator {
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult
// override to capture output
fun<T> evalWithIO(body: () -> T): T = body()
}
interface ReplEvaluator : ReplChecker {
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult
fun dependenciesAdded(classpath: List<File>)
// override to capture output
fun<T> evalWithIO(body: () -> T): T = body()
}
@@ -0,0 +1,61 @@
/*
* 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.
*/
package org.jetbrains.kotlin.cli.common.repl;
import com.google.common.collect.Maps;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor;
import java.io.PrintWriter;
import java.util.Map;
public class ReplClassLoader extends ClassLoader {
private final Map<JvmClassName, byte[]> classes = Maps.newLinkedHashMap();
public ReplClassLoader(@NotNull ClassLoader parent) {
super(parent);
}
@NotNull
@Override
protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
byte[] classBytes = classes.get(JvmClassName.byFqNameWithoutInnerClasses(name));
if (classBytes != null) {
return defineClass(name, classBytes, 0, classBytes.length);
}
else {
return super.findClass(name);
}
}
public void addClass(@NotNull JvmClassName className, @NotNull byte[] bytes) {
byte[] oldBytes = classes.put(className, bytes);
if (oldBytes != null) {
throw new IllegalStateException("Rewrite at key " + className);
}
}
public void dumpClasses(@NotNull PrintWriter writer) {
for (byte[] classBytes : classes.values()) {
new ClassReader(classBytes).accept(new TraceClassVisitor(writer), 0);
}
}
}
@@ -0,0 +1,52 @@
/*
* 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.
*/
package org.jetbrains.kotlin.cli.common.repl
import com.google.common.base.Throwables
fun <T> checkAndUpdateReplHistoryCollection(col: MutableList<Pair<ReplCodeLine, T>>, baseHistory: Iterable<ReplCodeLine>): Int? {
val baseHistoryIt = baseHistory.iterator()
var idx = 0
while (baseHistoryIt.hasNext()) {
val curLine = baseHistoryIt.next()
if (col[idx].first != curLine) return curLine.no
idx += 1
}
col.dropLast(col.size - idx)
return null
}
fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String {
val newTrace = arrayListOf<StackTraceElement>()
var skip = true
for ((i, element) in cause.stackTrace.withIndex().reversed()) {
if ("${element.className}.${element.methodName}" == startFromMethodName) {
skip = false
}
if (!skip) {
newTrace.add(element)
}
}
val resultingTrace = newTrace.reversed().dropLast(1)
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UsePropertyAccessSyntax")
(cause as java.lang.Throwable).setStackTrace(resultingTrace.toTypedArray())
return Throwables.getStackTraceAsString(cause)
}