Move out JVM debugger functionality

This commit is contained in:
Yan Zhulanow
2019-04-05 15:10:14 +03:00
parent 5843336d42
commit ae7550c5af
318 changed files with 1068 additions and 723 deletions
+2
View File
@@ -0,0 +1,2 @@
eval4j is a Java byte code interpreter written in Kotlin.
Its primary use case is implementing expression evaluation in debuggers.
+24
View File
@@ -0,0 +1,24 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
compile(kotlinStdlib())
compile(project(":compiler:backend"))
compile(files(toolsJar()))
compileOnly(intellijDep()) { includeJars("asm-all", rootProject = rootProject) }
testCompile(project(":kotlin-test:kotlin-test-junit"))
testCompile(commonDep("junit:junit"))
testCompile(intellijDep()) { includeJars("asm-all", rootProject = rootProject) }
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
projectTest(parallel = true) {
dependsOn(":dist")
workingDir = rootDir
}
@@ -0,0 +1,425 @@
/*
* 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.
*/
package org.jetbrains.eval4j
import org.jetbrains.org.objectweb.asm.Handle
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
class UnsupportedByteCodeException(message: String) : RuntimeException(message)
interface Eval {
fun loadClass(classType: Type): Value
fun loadString(str: String): Value
fun newInstance(classType: Type): Value
fun isInstanceOf(value: Value, targetType: Type): Boolean
fun newArray(arrayType: Type, size: Int): Value
fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List<Int>): Value
fun getArrayLength(array: Value): Value
fun getArrayElement(array: Value, index: Value): Value
fun setArrayElement(array: Value, index: Value, newValue: Value)
fun getStaticField(fieldDesc: FieldDescription): Value
fun setStaticField(fieldDesc: FieldDescription, newValue: Value)
fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List<Value>): Value
fun getField(instance: Value, fieldDesc: FieldDescription): Value
fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value)
fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List<Value>, invokespecial: Boolean = false): Value
}
class SingleInstructionInterpreter(private val eval: Eval) : Interpreter<Value>(API_VERSION) {
override fun newValue(type: Type?): Value? {
if (type == null) {
return NOT_A_VALUE
}
return makeNotInitializedValue(type)
}
override fun newOperation(insn: AbstractInsnNode): Value? {
return when (insn.opcode) {
ACONST_NULL -> {
return NULL_VALUE
}
ICONST_M1 -> int(-1)
ICONST_0 -> int(0)
ICONST_1 -> int(1)
ICONST_2 -> int(2)
ICONST_3 -> int(3)
ICONST_4 -> int(4)
ICONST_5 -> int(5)
LCONST_0 -> long(0)
LCONST_1 -> long(1)
FCONST_0 -> float(0.0f)
FCONST_1 -> float(1.0f)
FCONST_2 -> float(2.0f)
DCONST_0 -> double(0.0)
DCONST_1 -> double(1.0)
BIPUSH, SIPUSH -> int((insn as IntInsnNode).operand)
LDC -> {
val cst = ((insn as LdcInsnNode)).cst
when (cst) {
is Int -> int(cst)
is Float -> float(cst)
is Long -> long(cst)
is Double -> double(cst)
is String -> eval.loadString(cst)
is Type -> {
val sort = cst.sort
when (sort) {
Type.OBJECT, Type.ARRAY -> eval.loadClass(cst)
Type.METHOD -> throw UnsupportedByteCodeException("Method handles are not supported")
else -> throw UnsupportedByteCodeException("Illegal LDC constant " + cst)
}
}
is Handle -> throw UnsupportedByteCodeException("Method handles are not supported")
else -> throw UnsupportedByteCodeException("Illegal LDC constant " + cst)
}
}
JSR -> LabelValue((insn as JumpInsnNode).label)
GETSTATIC -> eval.getStaticField(FieldDescription(insn as FieldInsnNode))
NEW -> eval.newInstance(Type.getObjectType((insn as TypeInsnNode).desc))
else -> throw UnsupportedByteCodeException("$insn")
}
}
override fun copyOperation(insn: AbstractInsnNode, value: Value): Value {
return value
}
override fun unaryOperation(insn: AbstractInsnNode, value: Value): Value? {
return when (insn.opcode) {
INEG -> int(-value.int)
IINC -> int(value.int + (insn as IincInsnNode).incr)
L2I -> int(value.long.toInt())
F2I -> int(value.float.toInt())
D2I -> int(value.double.toInt())
I2B -> byte(value.int.toByte())
I2C -> char(value.int.toChar())
I2S -> short(value.int.toShort())
FNEG -> float(-value.float)
I2F -> float(value.int.toFloat())
L2F -> float(value.long.toFloat())
D2F -> float(value.double.toFloat())
LNEG -> long(-value.long)
I2L -> long(value.int.toLong())
F2L -> long(value.float.toLong())
D2L -> long(value.double.toLong())
DNEG -> double(-value.double)
I2D -> double(value.int.toDouble())
L2D -> double(value.long.toDouble())
F2D -> double(value.float.toDouble())
IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> {
// Handled by interpreter loop, see checkUnaryCondition()
null
}
// TODO: switch
TABLESWITCH,
LOOKUPSWITCH -> throw UnsupportedByteCodeException("Switch is not supported yet")
PUTSTATIC -> {
eval.setStaticField(FieldDescription(insn as FieldInsnNode), value)
null
}
GETFIELD -> eval.getField(value, FieldDescription(insn as FieldInsnNode))
NEWARRAY -> {
val typeStr = when ((insn as IntInsnNode).operand) {
T_BOOLEAN -> "[Z"
T_CHAR -> "[C"
T_BYTE -> "[B"
T_SHORT -> "[S"
T_INT -> "[I"
T_FLOAT -> "[F"
T_DOUBLE -> "[D"
T_LONG -> "[J"
else -> throw AnalyzerException(insn, "Invalid array type")
}
eval.newArray(Type.getType(typeStr), value.int)
}
ANEWARRAY -> {
val desc = (insn as TypeInsnNode).desc
eval.newArray(Type.getType("[" + Type.getObjectType(desc)), value.int)
}
ARRAYLENGTH -> eval.getArrayLength(value)
ATHROW -> {
// Handled by interpreter loop
null
}
CHECKCAST -> {
val targetType = Type.getObjectType((insn as TypeInsnNode).desc)
when {
value == NULL_VALUE -> NULL_VALUE
eval.isInstanceOf(value, targetType) -> ObjectValue(value.obj(), targetType)
else -> throwInterpretingException(ClassCastException("${value.asmType.className} cannot be cast to ${targetType.className}"))
}
}
INSTANCEOF -> {
val targetType = Type.getObjectType((insn as TypeInsnNode).desc)
boolean(eval.isInstanceOf(value, targetType))
}
// TODO: maybe just do nothing?
MONITORENTER, MONITOREXIT -> throw UnsupportedByteCodeException("Monitor instructions are not supported")
else -> throw UnsupportedByteCodeException("$insn")
}
}
fun checkUnaryCondition(value: Value, opcode: Int): Boolean {
return when (opcode) {
IFEQ -> value.int == 0
IFNE -> value.int != 0
IFLT -> value.int < 0
IFGT -> value.int > 0
IFLE -> value.int <= 0
IFGE -> value.int >= 0
IFNULL -> value.obj() == null
IFNONNULL -> value.obj() != null
else -> throw UnsupportedByteCodeException("Unknown opcode: $opcode")
}
}
private fun divisionByZero(): Nothing = throwInterpretingException(ArithmeticException("Division by zero"))
override fun binaryOperation(insn: AbstractInsnNode, value1: Value, value2: Value): Value? {
return when (insn.opcode) {
IALOAD, BALOAD, CALOAD, SALOAD,
FALOAD, LALOAD, DALOAD,
AALOAD -> eval.getArrayElement(value1, value2)
IADD -> int(value1.int + value2.int)
ISUB -> int(value1.int - value2.int)
IMUL -> int(value1.int * value2.int)
IDIV -> {
val divider = value2.int
if (divider == 0) {
divisionByZero()
}
int(value1.int / divider)
}
IREM -> {
val divider = value2.int
if (divider == 0) {
divisionByZero()
}
int(value1.int % divider)
}
ISHL -> int(value1.int shl value2.int)
ISHR -> int(value1.int shr value2.int)
IUSHR -> int(value1.int ushr value2.int)
IAND -> int(value1.int and value2.int)
IOR -> int(value1.int or value2.int)
IXOR -> int(value1.int xor value2.int)
LADD -> long(value1.long + value2.long)
LSUB -> long(value1.long - value2.long)
LMUL -> long(value1.long * value2.long)
LDIV -> {
val divider = value2.long
if (divider == 0L) {
divisionByZero()
}
long(value1.long / divider)
}
LREM -> {
val divider = value2.long
if (divider == 0L) {
divisionByZero()
}
long(value1.long % divider)
}
LSHL -> long(value1.long shl value2.int)
LSHR -> long(value1.long shr value2.int)
LUSHR -> long(value1.long ushr value2.int)
LAND -> long(value1.long and value2.long)
LOR -> long(value1.long or value2.long)
LXOR -> long(value1.long xor value2.long)
FADD -> float(value1.float + value2.float)
FSUB -> float(value1.float - value2.float)
FMUL -> float(value1.float * value2.float)
FDIV -> {
val divider = value2.float
if (divider == 0f) {
divisionByZero()
}
float(value1.float / divider)
}
FREM -> {
val divider = value2.float
if (divider == 0f) {
divisionByZero()
}
float(value1.float % divider)
}
DADD -> double(value1.double + value2.double)
DSUB -> double(value1.double - value2.double)
DMUL -> double(value1.double * value2.double)
DDIV -> {
val divider = value2.double
if (divider == 0.0) {
divisionByZero()
}
double(value1.double / divider)
}
DREM -> {
val divider = value2.double
if (divider == 0.0) {
divisionByZero()
}
double(value1.double % divider)
}
LCMP -> {
val l1 = value1.long
val l2 = value2.long
int(when {
l1 > l2 -> 1
l1 == l2 -> 0
else -> -1
})
}
FCMPL,
FCMPG -> {
val l1 = value1.float
val l2 = value2.float
int(when {
l1 > l2 -> 1
l1 == l2 -> 0
l1 < l2 -> -1
// one of them is NaN
else -> if (insn.opcode == FCMPG) 1 else -1
})
}
DCMPL,
DCMPG -> {
val l1 = value1.double
val l2 = value2.double
int(when {
l1 > l2 -> 1
l1 == l2 -> 0
l1 < l2 -> -1
// one of them is NaN
else -> if (insn.opcode == DCMPG) 1 else -1
})
}
IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> {
// Handled by interpreter loop, see checkBinaryCondition()
null
}
PUTFIELD -> {
eval.setField(value1, FieldDescription(insn as FieldInsnNode), value2)
null
}
else -> throw UnsupportedByteCodeException("$insn")
}
}
fun checkBinaryCondition(value1: Value, value2: Value, opcode: Int): Boolean {
return when (opcode) {
IF_ICMPEQ -> value1.int == value2.int
IF_ICMPNE -> value1.int != value2.int
IF_ICMPLT -> value1.int < value2.int
IF_ICMPGT -> value1.int > value2.int
IF_ICMPLE -> value1.int <= value2.int
IF_ICMPGE -> value1.int >= value2.int
IF_ACMPEQ -> value1.obj() == value2.obj()
IF_ACMPNE -> value1.obj() != value2.obj()
else -> throw UnsupportedByteCodeException("Unknown opcode: $opcode")
}
}
override fun ternaryOperation(insn: AbstractInsnNode, value1: Value, value2: Value, value3: Value): Value? {
return when (insn.opcode) {
IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE -> {
eval.setArrayElement(value1, value2, value3)
null
}
else -> throw UnsupportedByteCodeException("$insn")
}
}
override fun naryOperation(insn: AbstractInsnNode, values: List<Value>): Value {
return when (insn.opcode) {
MULTIANEWARRAY -> {
val node = insn as MultiANewArrayInsnNode
eval.newMultiDimensionalArray(Type.getType(node.desc), values.map(Value::int))
}
INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> {
eval.invokeMethod(
values[0],
MethodDescription(insn as MethodInsnNode),
values.subList(1, values.size),
insn.opcode == INVOKESPECIAL
)
}
INVOKESTATIC -> eval.invokeStaticMethod(MethodDescription(insn as MethodInsnNode), values)
INVOKEDYNAMIC -> throw UnsupportedByteCodeException("INVOKEDYNAMIC is not supported")
else -> throw UnsupportedByteCodeException("$insn")
}
}
override fun returnOperation(insn: AbstractInsnNode, value: Value, expected: Value) {
when (insn.opcode) {
IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> {
// Handled by interpreter loop
}
else -> throw UnsupportedByteCodeException("$insn")
}
}
override fun merge(v: Value, w: Value): Value {
// We always remember the NEW value
return w
}
}
@@ -0,0 +1,274 @@
/*
* 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.
*/
package org.jetbrains.eval4j
import org.jetbrains.eval4j.ExceptionThrown.ExceptionKind
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.util.Printer
import java.util.*
interface InterpreterResult {
override fun toString(): String
}
class ExceptionThrown(val exception: ObjectValue, val kind: ExceptionKind): InterpreterResult {
override fun toString(): String = "Thrown $exception: $kind"
enum class ExceptionKind {
FROM_EVALUATED_CODE,
FROM_EVALUATOR,
BROKEN_CODE
}
}
data class ValueReturned(val result: Value): InterpreterResult {
override fun toString(): String = "Returned $result"
}
class AbnormalTermination(val message: String): InterpreterResult {
override fun toString(): String = "Terminated abnormally: $message"
}
interface InterpretationEventHandler {
object NONE : InterpretationEventHandler {
override fun instructionProcessed(insn: AbstractInsnNode): InterpreterResult? = null
override fun exceptionThrown(currentState: Frame<Value>, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? = null
override fun exceptionCaught(currentState: Frame<Value>, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? = null
}
// If a non-null value is returned, interpreter loop is terminated and that value is used as a result
fun instructionProcessed(insn: AbstractInsnNode): InterpreterResult?
fun exceptionThrown(currentState: Frame<Value>, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult?
fun exceptionCaught(currentState: Frame<Value>, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult?
}
abstract class ThrownFromEvalExceptionBase(cause: Throwable): RuntimeException(cause) {
override fun toString(): String = "Thrown by evaluator: ${cause}"
}
class BrokenCode(cause: Throwable) : ThrownFromEvalExceptionBase(cause)
// Interpreting exceptions should not be sent to EA
class Eval4JInterpretingException(override val cause: Throwable) : RuntimeException(cause)
class ThrownFromEvaluatedCodeException(val exception: ObjectValue): RuntimeException() {
override fun toString(): String = "Thrown from evaluated code: $exception"
}
fun interpreterLoop(
m: MethodNode,
initialState: Frame<Value>,
eval: Eval,
handler: InterpretationEventHandler = InterpretationEventHandler.NONE
): InterpreterResult {
val firstInsn = m.instructions.first
if (firstInsn == null) throw IllegalArgumentException("Empty method")
var currentInsn = firstInsn
fun goto(nextInsn: AbstractInsnNode?) {
if (nextInsn == null) throw IllegalArgumentException("Instruction flow ended with no RETURN")
currentInsn = nextInsn
}
val interpreter = SingleInstructionInterpreter(eval)
val frame = Frame(initialState)
val handlers = computeHandlers(m)
class ResultException(val result: InterpreterResult): RuntimeException()
fun exceptionCaught(exceptionValue: Value, instanceOf: (Type) -> Boolean): Boolean {
val catchBlocks = handlers[m.instructions.indexOf(currentInsn)] ?: listOf()
for (catch in catchBlocks) {
val exceptionTypeInternalName = catch.type
if (exceptionTypeInternalName != null) {
val exceptionType = Type.getObjectType(exceptionTypeInternalName)
if (instanceOf(exceptionType)) {
val handled = handler.exceptionCaught(frame, currentInsn, exceptionValue)
if (handled != null) throw ResultException(handled)
frame.clearStack()
frame.push(exceptionValue)
goto(catch.handler)
return true
}
}
}
return false
}
fun exceptionCaught(exceptionValue: Value): Boolean = exceptionCaught(exceptionValue) {
exceptionType -> eval.isInstanceOf(exceptionValue, exceptionType)
}
fun exceptionFromEvalCaught(exception: Throwable, exceptionValue: Value): Boolean {
return exceptionCaught(exceptionValue) {
exceptionType ->
try {
val exceptionClass = exception::class.java
val _class = Class.forName(
exceptionType.internalName.replace('/', '.'),
true,
exceptionClass.classLoader
)
_class.isAssignableFrom(exceptionClass)
}
catch (e: ClassNotFoundException) {
// If the class is not available in this VM, it can not be a superclass of an exception trown in it
false
}
}
}
try {
loop@ while (true) {
val insnOpcode = currentInsn.opcode
val insnType = currentInsn.type
when (insnType) {
AbstractInsnNode.LABEL,
AbstractInsnNode.FRAME,
AbstractInsnNode.LINE -> {
// skip to the next instruction
}
else -> {
when (insnOpcode) {
GOTO -> {
goto((currentInsn as JumpInsnNode).label)
continue@loop
}
RET -> {
val varNode = currentInsn as VarInsnNode
val address = frame.getLocal(varNode.`var`)
goto((address as LabelValue).value)
continue@loop
}
// TODO: switch
LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet")
TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet")
IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> {
val value = frame.getStackTop()
val expectedType = Type.getReturnType(m.desc)
if (expectedType.sort == Type.OBJECT || expectedType.sort == Type.ARRAY) {
val coerced = if (value != NULL_VALUE && value.asmType != expectedType)
ObjectValue(value.obj(), expectedType)
else value
return ValueReturned(coerced)
}
if (value.asmType != expectedType) {
assert(insnOpcode == IRETURN) { "Only ints should be coerced: ${Printer.OPCODES[insnOpcode]}" }
val coerced = when (expectedType.sort) {
Type.BOOLEAN -> boolean(value.boolean)
Type.BYTE -> byte(value.int.toByte())
Type.SHORT -> short(value.int.toShort())
Type.CHAR -> char(value.int.toChar())
Type.INT -> int(value.int)
else -> throw UnsupportedByteCodeException("Should not be coerced: $expectedType")
}
return ValueReturned(coerced)
}
return ValueReturned(value)
}
RETURN -> return ValueReturned(VOID_VALUE)
IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> {
if (interpreter.checkUnaryCondition(frame.getStackTop(), insnOpcode)) {
frame.execute(currentInsn, interpreter)
goto((currentInsn as JumpInsnNode).label)
continue@loop
}
}
IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> {
if (interpreter.checkBinaryCondition(frame.getStackTop(1), frame.getStackTop(0), insnOpcode)) {
frame.execute(currentInsn, interpreter)
goto((currentInsn as JumpInsnNode).label)
continue@loop
}
}
ATHROW -> {
val exceptionValue = frame.getStackTop() as ObjectValue
val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue)
if (handled != null) return handled
if (exceptionCaught(exceptionValue)) continue@loop
return ExceptionThrown(exceptionValue, ExceptionKind.FROM_EVALUATED_CODE)
}
// Workaround for a bug in Kotlin: NoPatterMatched exception is thrown otherwise!
else -> {}
}
try {
frame.execute(currentInsn, interpreter)
}
catch (e: ThrownFromEvalExceptionBase) {
val exception = e.cause!!
val exceptionValue = ObjectValue(exception, Type.getType(exception::class.java))
val handled = handler.exceptionThrown(frame, currentInsn,
exceptionValue)
if (handled != null) return handled
if (exceptionFromEvalCaught(exception, exceptionValue)) continue@loop
val exceptionType = if (e is BrokenCode) ExceptionKind.BROKEN_CODE else ExceptionKind.FROM_EVALUATOR
return ExceptionThrown(exceptionValue, exceptionType)
}
catch (e: ThrownFromEvaluatedCodeException) {
val handled = handler.exceptionThrown(frame, currentInsn, e.exception)
if (handled != null) return handled
if (exceptionCaught(e.exception)) continue@loop
return ExceptionThrown(e.exception, ExceptionKind.FROM_EVALUATED_CODE)
}
}
}
val handled = handler.instructionProcessed(currentInsn)
if (handled != null) return handled
goto(currentInsn.next)
}
}
catch(e: ResultException) {
return e.result
}
}
private fun <T: Value> Frame<T>.getStackTop(i: Int = 0) = this.getStack(this.stackSize - 1 - i) ?: throwBrokenCodeException(IllegalArgumentException("Couldn't get value with index = $i from top of stack"))
// Copied from org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer.analyze()
fun computeHandlers(m: MethodNode): Array<out List<TryCatchBlockNode>?> {
val insns = m.instructions
val handlers = Array<MutableList<TryCatchBlockNode>?>(insns.size()) {null}
for (tcb in m.tryCatchBlocks) {
val begin = insns.indexOf(tcb.start)
val end = insns.indexOf(tcb.end)
for (i in begin..end - 1) {
val insnHandlers = handlers[i] ?: ArrayList<TryCatchBlockNode>()
handlers[i] = insnHandlers
insnHandlers.add(tcb)
}
}
return handlers
}
@@ -0,0 +1,518 @@
/*
* 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.
*/
package org.jetbrains.eval4j.jdi
import com.intellij.openapi.util.text.StringUtil
import com.sun.jdi.*
import org.jetbrains.eval4j.*
import org.jetbrains.eval4j.Value
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.canBeMangledInternalName
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.internalNameWithoutModuleSuffix
import org.jetbrains.org.objectweb.asm.Type
import java.lang.reflect.AccessibleObject
import com.sun.jdi.Type as jdi_Type
import com.sun.jdi.Value as jdi_Value
private val CLASS = Type.getType(Class::class.java)
private val OBJECT = Type.getType(Any::class.java)
private val BOOTSTRAP_CLASS_DESCRIPTORS = setOf("Ljava/lang/String;", "Ljava/lang/ClassLoader;", "Ljava/lang/Class;")
class JDIEval(
private val vm: VirtualMachine,
private val defaultClassLoader: ClassLoaderReference?,
private val thread: ThreadReference,
private val invokePolicy: Int
) : Eval {
private val primitiveTypes = mapOf(
Type.BOOLEAN_TYPE.className to vm.mirrorOf(true).type(),
Type.BYTE_TYPE.className to vm.mirrorOf(1.toByte()).type(),
Type.SHORT_TYPE.className to vm.mirrorOf(1.toShort()).type(),
Type.INT_TYPE.className to vm.mirrorOf(1).type(),
Type.CHAR_TYPE.className to vm.mirrorOf('1').type(),
Type.LONG_TYPE.className to vm.mirrorOf(1L).type(),
Type.FLOAT_TYPE.className to vm.mirrorOf(1.0f).type(),
Type.DOUBLE_TYPE.className to vm.mirrorOf(1.0).type()
)
private val isJava8OrLater = StringUtil.compareVersionNumbers(vm.version(), "1.8") >= 0
override fun loadClass(classType: Type): Value {
return loadClass(classType, defaultClassLoader)
}
fun loadClass(classType: Type, classLoader: ClassLoaderReference?): Value {
val loadedClasses = vm.classesByName(classType.internalName)
if (!loadedClasses.isEmpty()) {
for (loadedClass in loadedClasses) {
if (loadedClass.isPrepared && (classType.descriptor in BOOTSTRAP_CLASS_DESCRIPTORS || loadedClass.classLoader() == classLoader)) {
return loadedClass.classObject().asValue()
}
}
}
if (classLoader == null) {
return invokeStaticMethod(
MethodDescription(
CLASS.internalName,
"forName",
"(Ljava/lang/String;)Ljava/lang/Class;",
true
),
listOf(vm.mirrorOf(classType.internalName.replace('/', '.')).asValue())
)
}
else {
return invokeStaticMethod(
MethodDescription(
CLASS.internalName,
"forName",
"(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;",
true
),
listOf(
vm.mirrorOf(classType.internalName.replace('/', '.')).asValue(),
boolean(true),
classLoader.asValue()
)
)
}
}
fun loadClassByName(name: String, classLoader: ClassLoaderReference): jdi_Type {
val dimensions = name.count { it == '[' }
val baseTypeName = if (dimensions > 0) name.substring(0, name.indexOf('[')) else name
val baseType = primitiveTypes[baseTypeName] ?: Type.getType("L$baseTypeName;").asReferenceType(classLoader)
return if (dimensions == 0)
baseType
else
Type.getType("[".repeat(dimensions) + baseType.asType().descriptor).asReferenceType(classLoader)
}
override fun loadString(str: String): Value = vm.mirrorOf(str).asValue()
override fun newInstance(classType: Type): Value {
return NewObjectValue(classType)
}
override fun isInstanceOf(value: Value, targetType: Type): Boolean {
assert(targetType.sort == Type.OBJECT || targetType.sort == Type.ARRAY) {
"Can't check isInstanceOf() for non-object type $targetType"
}
val _class = loadClass(targetType)
return invokeMethod(
_class,
MethodDescription(
CLASS.internalName,
"isInstance",
"(Ljava/lang/Object;)Z",
false
),
listOf(value)).boolean
}
fun Type.asReferenceType(classLoader: ClassLoaderReference? = this@JDIEval.defaultClassLoader): ReferenceType = loadClass(this, classLoader).jdiClass!!.reflectedType()
fun Type.asArrayType(classLoader: ClassLoaderReference? = this@JDIEval.defaultClassLoader): ArrayType = asReferenceType(classLoader) as ArrayType
override fun newArray(arrayType: Type, size: Int): Value {
val jdiArrayType = arrayType.asArrayType()
return jdiArrayType.newInstance(size).asValue()
}
private val Type.arrayElementType: Type
get(): Type {
assert(sort == Type.ARRAY) { "Not an array type: $this" }
return Type.getType(descriptor.substring(1))
}
private fun fillArray(elementType: Type, size: Int, nestedSizes: List<Int>): Value {
val arr = newArray(Type.getType("[" + elementType.descriptor), size)
if (!nestedSizes.isEmpty()) {
val nestedElementType = elementType.arrayElementType
val nestedSize = nestedSizes[0]
val tail = nestedSizes.drop(1)
for (i in 0..size - 1) {
setArrayElement(arr, int(i), fillArray(nestedElementType, nestedSize, tail))
}
}
return arr
}
override fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List<Int>): Value {
return fillArray(arrayType.arrayElementType, dimensionSizes[0], dimensionSizes.drop(1))
}
private fun Value.array() = jdiObj.checkNull() as ArrayReference
override fun getArrayLength(array: Value): Value {
return int(array.array().length())
}
override fun getArrayElement(array: Value, index: Value): Value {
try {
return array.array().getValue(index.int).asValue()
}
catch (e: IndexOutOfBoundsException) {
throwInterpretingException(ArrayIndexOutOfBoundsException(e.message))
}
}
override fun setArrayElement(array: Value, index: Value, newValue: Value) {
try {
return array.array().setValue(index.int, newValue.asJdiValue(vm, array.asmType.arrayElementType))
}
catch (e: IndexOutOfBoundsException) {
throwInterpretingException(ArrayIndexOutOfBoundsException(e.message))
}
}
private fun findField(fieldDesc: FieldDescription, receiver: ReferenceType? = null): Field {
for (owner in listOfNotNull(receiver, fieldDesc.ownerType.asReferenceType())) {
owner.fieldByName(fieldDesc.name)?.let { return it }
}
throwBrokenCodeException(NoSuchFieldError("Field not found: $fieldDesc"))
}
private fun findStaticField(fieldDesc: FieldDescription): Field {
val field = findField(fieldDesc)
if (!field.isStatic) {
throwBrokenCodeException(NoSuchFieldError("Field is not static: $fieldDesc"))
}
return field
}
override fun getStaticField(fieldDesc: FieldDescription): Value {
val field = findStaticField(fieldDesc)
return mayThrow { field.declaringType().getValue(field) }.ifFail(field).asValue()
}
override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) {
val field = findStaticField(fieldDesc)
if (field.isFinal) {
throwBrokenCodeException(NoSuchFieldError("Can't modify a final field: $field"))
}
val _class = field.declaringType()
if (_class !is ClassType) {
throwBrokenCodeException(NoSuchFieldError("Can't a field in a non-class: $field"))
}
val jdiValue = newValue.asJdiValue(vm, field.type().asType())
mayThrow { _class.setValue(field, jdiValue) }.ifFail(field)
}
private fun findMethod(methodDesc: MethodDescription, _class: ReferenceType = methodDesc.ownerType.asReferenceType()): Method {
val methodName = methodDesc.name
val method = when (_class) {
is ClassType ->
_class.concreteMethodByName(methodName, methodDesc.desc)
else ->
_class.methodsByName(methodName, methodDesc.desc).firstOrNull()
}
if (method != null) {
return method
}
// Module name can be different for internal functions during evaluation and compilation
val internalNameWithoutSuffix = internalNameWithoutModuleSuffix(methodName)
if (internalNameWithoutSuffix != null) {
val internalMethods = _class.visibleMethods().filter {
val name = it.name()
name.startsWith(internalNameWithoutSuffix) && canBeMangledInternalName(name) && it.signature() == methodDesc.desc
}
if (!internalMethods.isEmpty()) {
return internalMethods.singleOrNull() ?:
throwBrokenCodeException(IllegalArgumentException("Several internal methods found for $methodDesc"))
}
}
throwBrokenCodeException(NoSuchMethodError("Method not found: $methodDesc"))
}
override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List<Value>): Value {
val method = findMethod(methodDesc)
if (!method.isStatic) {
throwBrokenCodeException(NoSuchMethodError("Method is not static: $methodDesc"))
}
val declaringType = method.declaringType()
val args = mapArguments(arguments, method.safeArgumentTypes())
if (shouldInvokeMethodWithReflection(method, args)) {
return invokeMethodWithReflection(declaringType.asType(), NULL_VALUE, args, methodDesc)
}
args.disableCollection()
val result = mayThrow {
when (declaringType) {
is ClassType -> declaringType.invokeMethod(thread, method, args, invokePolicy)
is InterfaceType -> {
if (!isJava8OrLater) {
val message = "Calling interface static methods is not supported in JVM ${vm.version()} ($method)"
throwBrokenCodeException(NoSuchMethodError(message))
}
declaringType.invokeMethod(thread, method, args, invokePolicy)
}
else -> {
val message = "Calling static methods is only supported for classes and interfaces ($method)"
throwBrokenCodeException(NoSuchMethodError(message))
}
}
}.ifFail(method)
args.enableCollection()
return result.asValue()
}
override fun getField(instance: Value, fieldDesc: FieldDescription): Value {
val receiver = instance.jdiObj.checkNull()
val field = findField(fieldDesc, receiver.referenceType())
return mayThrow {
try {
receiver.getValue(field)
} catch (e: IllegalArgumentException) {
throw IllegalArgumentException("Possibly incompatible types: " +
"field declaring type = ${field.declaringType()}, " +
"instance type = ${receiver.referenceType()}")
}
}.ifFail(field, receiver).asValue()
}
override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) {
val receiver = instance.jdiObj.checkNull()
val field = findField(fieldDesc, receiver.referenceType())
val jdiValue = newValue.asJdiValue(vm, field.type().asType())
mayThrow { receiver.setValue(field, jdiValue) }
}
fun unboxType(boxedValue: Value, type: Type): Value {
val method = when (type) {
Type.INT_TYPE -> MethodDescription("java/lang/Integer", "intValue", "()I", false)
Type.BOOLEAN_TYPE -> MethodDescription("java/lang/Boolean", "booleanValue", "()Z", false)
Type.CHAR_TYPE -> MethodDescription("java/lang/Character", "charValue", "()C", false)
Type.SHORT_TYPE -> MethodDescription("java/lang/Character", "shortValue", "()S", false)
Type.LONG_TYPE -> MethodDescription("java/lang/Long", "longValue", "()J", false)
Type.BYTE_TYPE -> MethodDescription("java/lang/Byte", "byteValue", "()B", false)
Type.FLOAT_TYPE -> MethodDescription("java/lang/Float", "floatValue", "()F", false)
Type.DOUBLE_TYPE -> MethodDescription("java/lang/Double", "doubleValue", "()D", false)
else -> throw UnsupportedOperationException("Couldn't unbox non primitive type ${type.internalName}")
}
return invokeMethod(boxedValue, method, listOf(), true)
}
fun boxType(value: Value): Value {
val method = when (value.asmType) {
Type.INT_TYPE -> MethodDescription("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
Type.BYTE_TYPE -> MethodDescription("java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false)
Type.SHORT_TYPE -> MethodDescription("java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false)
Type.LONG_TYPE -> MethodDescription("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false)
Type.BOOLEAN_TYPE -> MethodDescription("java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false)
Type.CHAR_TYPE -> MethodDescription("java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false)
Type.FLOAT_TYPE -> MethodDescription("java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false)
Type.DOUBLE_TYPE -> MethodDescription("java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false)
else -> throw UnsupportedOperationException("Couldn't box non-primitive type ${value.asmType.internalName}")
}
return invokeStaticMethod(method, listOf(value))
}
override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List<Value>, invokespecial: Boolean): Value {
if (invokespecial && methodDesc.name == "<init>") {
// Constructor call
val ctor = findMethod(methodDesc)
val _class = (instance as NewObjectValue).asmType.asReferenceType() as ClassType
val args = mapArguments(arguments, ctor.safeArgumentTypes())
args.disableCollection()
val result = mayThrow { _class.newInstance(thread, ctor, args, invokePolicy) }.ifFail(ctor)
args.enableCollection()
instance.value = result
return result.asValue()
}
fun doInvokeMethod(obj: ObjectReference, method: Method, policy: Int): Value {
val args = mapArguments(arguments, method.safeArgumentTypes())
if (shouldInvokeMethodWithReflection(method, args)) {
return invokeMethodWithReflection(instance.asmType, instance, args, methodDesc)
}
args.disableCollection()
val result = mayThrow { obj.invokeMethod(thread, method, args, policy) }.ifFail(method, obj)
args.enableCollection()
return result.asValue()
}
val obj = instance.jdiObj.checkNull()
return if (invokespecial) {
val method = findMethod(methodDesc)
doInvokeMethod(obj, method, invokePolicy or ObjectReference.INVOKE_NONVIRTUAL)
}
else {
val method = findMethod(methodDesc, obj.referenceType() ?: methodDesc.ownerType.asReferenceType())
doInvokeMethod(obj, method, invokePolicy)
}
}
private fun shouldInvokeMethodWithReflection(method: Method, args: List<com.sun.jdi.Value?>): Boolean {
if (method.isVarArgs) {
return false
}
val argumentTypes = try {
method.argumentTypes()
} catch (e: ClassNotLoadedException) {
return false
}
return args.zip(argumentTypes).any { isArrayOfInterfaces(it.first?.type(), it.second) }
}
private fun isArrayOfInterfaces(valueType: jdi_Type?, expectedType: jdi_Type?): Boolean {
return (valueType as? ArrayType)?.componentType() is InterfaceType && (expectedType as? ArrayType)?.componentType() == OBJECT.asReferenceType()
}
private fun invokeMethodWithReflection(ownerType: Type, instance: Value, args: List<jdi_Value?>, methodDesc: MethodDescription): Value {
val methodToInvoke = invokeMethod(
loadClass(ownerType),
MethodDescription(
CLASS.internalName,
"getDeclaredMethod",
"(Ljava/lang/String;[L${CLASS.internalName};)Ljava/lang/reflect/Method;",
true
),
listOf(vm.mirrorOf(methodDesc.name).asValue(), *methodDesc.parameterTypes.map { loadClass(it) }.toTypedArray())
)
invokeMethod(
methodToInvoke,
MethodDescription(
Type.getType(AccessibleObject::class.java).internalName,
"setAccessible",
"(Z)V",
true
),
listOf(vm.mirrorOf(true).asValue())
)
val invocationResult = invokeMethod(
methodToInvoke,
MethodDescription(
methodToInvoke.asmType.internalName,
"invoke",
"(L${OBJECT.internalName};[L${OBJECT.internalName};)L${OBJECT.internalName};",
true
),
listOf(instance, *args.map { it.asValue() }.toTypedArray())
)
if (methodDesc.returnType.sort != Type.OBJECT && methodDesc.returnType.sort != Type.ARRAY && methodDesc.returnType.sort != Type.VOID) {
return unboxType(invocationResult, methodDesc.returnType)
}
return invocationResult
}
private fun List<jdi_Value?>.disableCollection() {
forEach { (it as? ObjectReference)?.disableCollection() }
}
private fun List<jdi_Value?>.enableCollection() {
forEach { (it as? ObjectReference)?.enableCollection() }
}
private fun mapArguments(arguments: List<Value>, expectedTypes: List<jdi_Type>): List<jdi_Value?> {
return arguments.zip(expectedTypes).map {
val (arg, expectedType) = it
arg.asJdiValue(vm, expectedType.asType())
}
}
private fun Method.safeArgumentTypes(): List<jdi_Type> {
try {
return argumentTypes()
} catch (e: ClassNotLoadedException) {
return argumentTypeNames()!!.map { name ->
val classLoader = declaringType()?.classLoader()
if (classLoader != null) {
return@map loadClassByName(name, classLoader)
}
when (name) {
"void" -> virtualMachine().mirrorOfVoid().type()
"boolean" -> primitiveTypes.getValue(Type.BOOLEAN_TYPE.className)
"byte" -> primitiveTypes.getValue(Type.BYTE_TYPE.className)
"char" -> primitiveTypes.getValue(Type.CHAR_TYPE.className)
"short" -> primitiveTypes.getValue(Type.SHORT_TYPE.className)
"int" -> primitiveTypes.getValue(Type.INT_TYPE.className)
"long" -> primitiveTypes.getValue(Type.LONG_TYPE.className)
"float" -> primitiveTypes.getValue(Type.FLOAT_TYPE.className)
"double" -> primitiveTypes.getValue(Type.DOUBLE_TYPE.className)
else -> virtualMachine().classesByName(name).firstOrNull()
?: throw IllegalStateException("Unknown class $name")
}
}
}
}
}
@Suppress("unused")
private sealed class JdiOperationResult<T> {
class Fail<T>(val cause: Exception): JdiOperationResult<T>()
class OK<T>(val value: T): JdiOperationResult<T>()
}
private fun <T> mayThrow(f: () -> T): JdiOperationResult<T> {
try {
return JdiOperationResult.OK(f())
}
catch (e: IllegalArgumentException) {
return JdiOperationResult.Fail<T>(e)
}
catch (e: InvocationException) {
throw ThrownFromEvaluatedCodeException(e.exception().asValue())
}
}
private fun memberInfo(member: TypeComponent, thisObj: ObjectReference?): String {
return "\nmember = $member\nobjectRef = $thisObj"
}
private fun <T> JdiOperationResult<T>.ifFail(member: TypeComponent, thisObj: ObjectReference? = null): T {
return ifFail { memberInfo(member, thisObj) }
}
private fun <T> JdiOperationResult<T>.ifFail(lazyMessage: () -> String): T {
return when(this) {
is JdiOperationResult.OK -> this.value
is JdiOperationResult.Fail -> {
if (cause is IllegalArgumentException) {
throwBrokenCodeException(IllegalArgumentException(lazyMessage(), this.cause))
}
else {
throwBrokenCodeException(IllegalStateException(lazyMessage(), this.cause))
}
}
}
}
@@ -0,0 +1,118 @@
/*
* 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.
*/
package org.jetbrains.eval4j.jdi
import com.sun.jdi.ClassObjectReference
import com.sun.jdi.VirtualMachine
import org.jetbrains.eval4j.*
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_STATIC
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import com.sun.jdi.BooleanValue as jdi_BooleanValue
import com.sun.jdi.ByteValue as jdi_ByteValue
import com.sun.jdi.CharValue as jdi_CharValue
import com.sun.jdi.DoubleValue as jdi_DoubleValue
import com.sun.jdi.FloatValue as jdi_FloatValue
import com.sun.jdi.IntegerValue as jdi_IntegerValue
import com.sun.jdi.LongValue as jdi_LongValue
import com.sun.jdi.ObjectReference as jdi_ObjectReference
import com.sun.jdi.ShortValue as jdi_ShortValue
import com.sun.jdi.Type as jdi_Type
import com.sun.jdi.Value as jdi_Value
import com.sun.jdi.VoidValue as jdi_VoidValue
fun makeInitialFrame(methodNode: MethodNode, arguments: List<Value>): Frame<Value> {
val isStatic = (methodNode.access and ACC_STATIC) != 0
val params = Type.getArgumentTypes(methodNode.desc)
assert(arguments.size == (if (isStatic) params.size else params.size + 1)) {
"Wrong number of arguments for $methodNode: $arguments"
}
val frame = Frame<Value>(methodNode.maxLocals, methodNode.maxStack)
frame.setReturn(makeNotInitializedValue(Type.getReturnType(methodNode.desc)))
var index = 0
for (arg in arguments) {
frame.setLocal(index++, arg)
if (arg.size == 2) {
frame.setLocal(index++, NOT_A_VALUE)
}
}
while (index < methodNode.maxLocals) {
frame.setLocal(index++, NOT_A_VALUE)
}
return frame
}
class JDIFailureException(message: String?, cause: Throwable? = null): RuntimeException(message, cause)
fun jdi_ObjectReference?.asValue(): ObjectValue {
return when (this) {
null -> NULL_VALUE
else -> ObjectValue(this, type().asType())
}
}
fun jdi_Value?.asValue(): Value {
return when (this) {
null -> NULL_VALUE
is jdi_VoidValue -> VOID_VALUE
is jdi_BooleanValue -> IntValue(intValue(), Type.BOOLEAN_TYPE)
is jdi_ByteValue -> IntValue(intValue(), Type.BYTE_TYPE)
is jdi_ShortValue -> IntValue(intValue(), Type.SHORT_TYPE)
is jdi_CharValue -> IntValue(intValue(), Type.CHAR_TYPE)
is jdi_IntegerValue -> IntValue(intValue(), Type.INT_TYPE)
is jdi_LongValue -> LongValue(longValue())
is jdi_FloatValue -> FloatValue(floatValue())
is jdi_DoubleValue -> DoubleValue(doubleValue())
is jdi_ObjectReference -> this.asValue()
else -> throw JDIFailureException("Unknown value: $this")
}
}
fun jdi_Type.asType(): Type = Type.getType(this.signature())
val Value.jdiObj: jdi_ObjectReference?
get() = this.obj() as jdi_ObjectReference?
val Value.jdiClass: ClassObjectReference?
get() = this.jdiObj as ClassObjectReference?
fun Value.asJdiValue(vm: VirtualMachine, expectedType: Type): jdi_Value? {
return when (this) {
NULL_VALUE -> null
VOID_VALUE -> vm.mirrorOfVoid()
is IntValue -> when (expectedType) {
Type.BOOLEAN_TYPE -> vm.mirrorOf(boolean)
Type.BYTE_TYPE -> vm.mirrorOf(int.toByte())
Type.SHORT_TYPE -> vm.mirrorOf(int.toShort())
Type.CHAR_TYPE -> vm.mirrorOf(int.toChar())
Type.INT_TYPE -> vm.mirrorOf(int)
else -> throw JDIFailureException("Unknown value type: $this")
}
is LongValue -> vm.mirrorOf(value)
is FloatValue -> vm.mirrorOf(value)
is DoubleValue -> vm.mirrorOf(value)
is ObjectValue -> value as jdi_ObjectReference
is NewObjectValue -> this.obj() as jdi_ObjectReference
else -> throw JDIFailureException("Unknown value: $this")
}
}
@@ -0,0 +1,93 @@
/*
* 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.
*/
package org.jetbrains.eval4j
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
open class MemberDescription protected constructor(
val ownerInternalName: String,
val name: String,
val desc: String,
val isStatic: Boolean
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
return (other is MemberDescription
&& ownerInternalName == other.ownerInternalName
&& name == other.name
&& desc == other.desc
&& isStatic == other.isStatic
)
}
override fun hashCode(): Int {
var result = 13
result = result * 23 + ownerInternalName.hashCode()
result = result * 23 + name.hashCode()
result = result * 23 + desc.hashCode()
result = result * 23 + isStatic.hashCode()
return result
}
override fun toString() = "MemberDescription(ownerInternalName = $ownerInternalName, name = $name, desc = $desc, isStatic = $isStatic)"
}
val MemberDescription.ownerType: Type
get() = Type.getObjectType(ownerInternalName)
class MethodDescription(
ownerInternalName: String,
name: String,
desc: String,
isStatic: Boolean
) : MemberDescription(ownerInternalName, name, desc, isStatic)
fun MethodDescription(insn: MethodInsnNode): MethodDescription =
MethodDescription(
insn.owner,
insn.name,
insn.desc,
insn.opcode == INVOKESTATIC
)
val MethodDescription.returnType: Type
get() = Type.getReturnType(desc)
val MethodDescription.parameterTypes: List<Type>
get() = Type.getArgumentTypes(desc).toList()
class FieldDescription(
ownerInternalName: String,
name: String,
desc: String,
isStatic: Boolean
) : MemberDescription(ownerInternalName, name, desc, isStatic)
fun FieldDescription(insn: FieldInsnNode): FieldDescription =
FieldDescription(
insn.owner,
insn.name,
insn.desc,
insn.opcode in setOf(GETSTATIC, PUTSTATIC)
)
val FieldDescription.fieldType: Type
get() = Type.getType(desc)
@@ -0,0 +1,134 @@
/*
* 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.
*/
package org.jetbrains.eval4j
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.LabelNode
interface Value : org.jetbrains.org.objectweb.asm.tree.analysis.Value {
val asmType: Type
val valid: Boolean
override fun getSize(): Int = asmType.size
override fun toString(): String
}
object NOT_A_VALUE: Value {
override val asmType = Type.getObjectType("<invalid>")
override val valid = false
override fun getSize(): Int = 1
override fun toString() = "NOT_A_VALUE"
}
object VOID_VALUE: Value {
override val asmType: Type = Type.VOID_TYPE
override val valid: Boolean = false
override fun toString() = "VOID_VALUE"
}
fun makeNotInitializedValue(t: Type): Value? {
return when (t.sort) {
Type.VOID -> null
else -> NotInitialized(t)
}
}
class NotInitialized(override val asmType: Type): Value {
override val valid = false
override fun toString() = "NotInitialized: $asmType"
}
abstract class AbstractValueBase<out V>(
override val asmType: Type
) : Value {
override val valid = true
abstract val value: V
override fun toString() = "$value: $asmType"
override fun equals(other: Any?): Boolean {
if (other !is AbstractValue<*>) return false
return value == other.value && asmType == other.asmType
}
override fun hashCode(): Int {
return value!!.hashCode() + 17 * asmType.hashCode()
}
}
abstract class AbstractValue<V>(
override val value: V,
asmType: Type
) : AbstractValueBase<V>(asmType)
class IntValue(value: Int, asmType: Type): AbstractValue<Int>(value, asmType)
class LongValue(value: Long): AbstractValue<Long>(value, Type.LONG_TYPE)
class FloatValue(value: Float): AbstractValue<Float>(value, Type.FLOAT_TYPE)
class DoubleValue(value: Double): AbstractValue<Double>(value, Type.DOUBLE_TYPE)
open class ObjectValue(value: Any?, asmType: Type): AbstractValue<Any?>(value, asmType)
class NewObjectValue(asmType: Type): ObjectValue(null, asmType) {
override var value: Any? = null
get(): Any? {
return field ?: throw IllegalStateException("Trying to access an unitialized object: $this")
}
}
class LabelValue(value: LabelNode): AbstractValue<LabelNode>(value, Type.VOID_TYPE)
fun boolean(v: Boolean) = IntValue(if (v) 1 else 0, Type.BOOLEAN_TYPE)
fun byte(v: Byte) = IntValue(v.toInt(), Type.BYTE_TYPE)
fun short(v: Short) = IntValue(v.toInt(), Type.SHORT_TYPE)
fun char(v: Char) = IntValue(v.toInt(), Type.CHAR_TYPE)
fun int(v: Int) = IntValue(v, Type.INT_TYPE)
fun long(v: Long) = LongValue(v)
fun float(v: Float) = FloatValue(v)
fun double(v: Double) = DoubleValue(v)
//fun <T> obj(v: T, t: Type = if (v != null) Type.getType(v.javaClass) else Type.getType(Any::class.java)) = ObjectValue(v, t)
val NULL_VALUE = ObjectValue(null, Type.getObjectType("null"))
val Value.boolean: Boolean get() = (this as IntValue).value == 1
val Value.int: Int get() = (this as IntValue).value
val Value.long: Long get() = (this as LongValue).value
val Value.float: Float get() = (this as FloatValue).value
val Value.double: Double get() = (this as DoubleValue).value
fun Value.obj(expectedType: Type = asmType): Any? {
return when {
expectedType == Type.BOOLEAN_TYPE -> this.boolean
expectedType == Type.SHORT_TYPE -> (this as IntValue).int.toShort()
expectedType == Type.BYTE_TYPE -> (this as IntValue).int.toByte()
expectedType == Type.CHAR_TYPE -> (this as IntValue).int.toChar()
else -> (this as AbstractValue<*>).value
}
}
fun <T : Any> T?.checkNull(): T {
if (this == null) {
throwInterpretingException(NullPointerException())
}
return this
}
fun throwInterpretingException(e: Throwable): Nothing {
throw Eval4JInterpretingException(e)
}
fun throwBrokenCodeException(e: Throwable): Nothing {
throw BrokenCode(e)
}
@@ -0,0 +1,25 @@
/*
* 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.
*/
package org.jetbrains.eval4j.jdi.test;
public class Debugee {
public static void main(String[] args) {
// BREAKPOINT
Runtime.getRuntime();
System.out.println("hi");
}
}
@@ -0,0 +1,164 @@
/*
* 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.
*/
package org.jetbrains.eval4j.jdi.test
import com.sun.jdi.*
import com.sun.jdi.event.BreakpointEvent
import com.sun.jdi.event.ClassPrepareEvent
import junit.framework.TestCase
import junit.framework.TestSuite
import org.jetbrains.eval4j.*
import org.jetbrains.eval4j.jdi.JDIEval
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.eval4j.jdi.jdiObj
import org.jetbrains.eval4j.jdi.makeInitialFrame
import org.jetbrains.eval4j.test.buildTestSuite
import org.jetbrains.eval4j.test.getTestName
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import java.io.File
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicInteger
val DEBUGEE_CLASS = Debugee::class.java
fun suite(): TestSuite {
val connectors = Bootstrap.virtualMachineManager().launchingConnectors()
val connector = connectors[0]
println("Using connector $connector")
val connectorArgs = connector.defaultArguments()
val debugeeName = DEBUGEE_CLASS.name
println("Debugee name: $debugeeName")
connectorArgs["main"]!!.setValue(debugeeName)
connectorArgs["options"]!!.setValue("-classpath out/production/eval4j${File.pathSeparator}out/test/eval4j")
connectorArgs["vmexec"]!!.setValue(connectorArgs["home"]!!.value() + File.separator + "bin" + File.separator + connectorArgs["vmexec"]!!.value())
connectorArgs["home"]!!.setValue("")
val vm = connector.launch(connectorArgs)!!
val req = vm.eventRequestManager().createClassPrepareRequest()
req.addClassFilter("*.Debugee")
req.enable()
val latch = CountDownLatch(1)
var classLoader : ClassLoaderReference? = null
var thread : ThreadReference? = null
Thread {
val eventQueue = vm.eventQueue()
mainLoop@ while (true) {
val eventSet = eventQueue.remove()
for (event in eventSet.eventIterator()) {
when (event) {
is ClassPrepareEvent -> {
val _class = event.referenceType()!!
if (_class.name() == debugeeName) {
for (l in _class.allLineLocations()) {
if (l.method().name() == "main") {
classLoader = l.method().declaringType().classLoader()
val breakpointRequest = vm.eventRequestManager().createBreakpointRequest(l)
breakpointRequest.enable()
println("Breakpoint: $breakpointRequest")
vm.resume()
break
}
}
}
}
is BreakpointEvent -> {
println("Suspended at: " + event.location())
thread = event.thread()
latch.countDown()
break@mainLoop
}
else -> {}
}
}
}
}.start()
vm.resume()
latch.await()
var remainingTests = AtomicInteger(0)
val suite = buildTestSuite {
methodNode, ownerClass, expected ->
remainingTests.incrementAndGet()
object : TestCase(getTestName(methodNode.name)) {
override fun runTest() {
val eval = JDIEval(vm, classLoader!!, thread!!, 0)
val args = if ((methodNode.access and Opcodes.ACC_STATIC) == 0) {
// Instance method
val newInstance = eval.newInstance(Type.getType(ownerClass))
val thisValue = eval.invokeMethod(newInstance, MethodDescription(ownerClass.name, "<init>", "()V", false), listOf(), true)
listOf(thisValue)
}
else {
listOf()
}
val value = interpreterLoop(
methodNode,
makeInitialFrame(methodNode, args),
eval
)
fun ObjectReference?.callToString(): String? {
if (this == null) return "null"
return (eval.invokeMethod(
this.asValue(),
MethodDescription(
"java/lang/Object",
"toString",
"()Ljava/lang/String;",
false
),
listOf()).jdiObj as StringReference).value()
}
try {
if (expected is ValueReturned && value is ValueReturned && value.result is ObjectValue) {
assertEquals(expected.result.obj().toString(), value.result.jdiObj.callToString())
}
else if (expected is ExceptionThrown && value is ExceptionThrown) {
val valueObj = value.exception.obj()
val actual = if (valueObj is ObjectReference) valueObj.callToString() else valueObj.toString()
assertEquals(expected.exception.obj().toString(), actual)
}
else {
assertEquals(expected, value)
}
}
finally {
if (remainingTests.decrementAndGet() == 0) vm.resume()
}
}
}
}
return suite
}
@@ -0,0 +1,32 @@
/*
* 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.
*/
package org.jetbrains.eval4j.test;
import java.lang.reflect.Array;
public class ArrayHelper {
public static Object newMultiArray(Class<?> elementType, Integer... dimensions) {
int[] dims = new int[dimensions.length];
int i = 0;
for (Integer dimension : dimensions) {
dims[i] = dimension;
i++;
}
return Array.newInstance(elementType, dims);
}
}
@@ -0,0 +1,25 @@
/*
* 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.
*/
package org.jetbrains.eval4j.test;
class BaseTestData {
String superCall() {
return "Base";
}
private String invokeSpecialPrivateFun(String s) { return "Derived"; }
}
@@ -0,0 +1,31 @@
/*
* 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.
*/
package org.jetbrains.eval4j.test;
import junit.framework.TestSuite;
import org.jetbrains.eval4j.jdi.test.JdiTestKt;
public class Eval4jTest extends TestSuite {
@SuppressWarnings({"UnnecessaryFullyQualifiedName", "StaticMethodReferencedViaSubclass"})
public static TestSuite suite() {
TestSuite eval4jSuite = new TestSuite("Eval4j Tests");
eval4jSuite.addTest(JdiTestKt.suite());
eval4jSuite.addTest(MainKt.suite());
return eval4jSuite;
}
}
@@ -0,0 +1,24 @@
/*
* 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.
*/
package org.jetbrains.eval4j.test;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreInReflectionTests {
}
@@ -0,0 +1,828 @@
/*
* 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.
*/
package org.jetbrains.eval4j.test;
class TestData extends BaseTestData {
static void returnVoid() {
}
static boolean returnBoolean() {
return true;
}
static byte returnByte() {
return 2;
}
static short returnShort() {
return 2;
}
static char returnChar() {
return '2';
}
static int returnInt() {
return 2;
}
static long returnLong() {
return 2;
}
static float returnFloat() {
return 2.0f;
}
static double returnDouble() {
return 2.0d;
}
static Object returnNull() {
return null;
}
static String returnString() {
return "str";
}
static Object returnStringAsObject() {
return "str";
}
static void checkCastNull() {
CheckCastToNull klass = new CheckCastToNull();
klass.f1(null);
klass.f2(null);
klass.f3(null);
Integer integer = (Integer) null;
Object object = (Object) null;
}
static class CheckCastToNull {
void f1(Integer p) {}
void f2(Integer[] p) {}
void f3(Integer[][] p) {}
}
static Integer integerValueOf() { return 1; }
static Byte byteValueOf() { return 1; }
static Short shortValueOf() { return 1; }
static Long longValueOf() { return 1L; }
static Float floatValueOf() { return 1.0f; }
static Double doubleValueOf() { return 1.0; }
static Character charValueOf() { return 1; }
static Boolean booleanValueOf() { return true; }
static void castFieldTypes() {
CastFieldType.i = 1;
CastFieldType.b = 1;
CastFieldType.s = 1;
CastFieldType.c = 1;
CastFieldType.bool = true;
CastFieldType.l = 1;
CastFieldType.f = 1.0f;
CastFieldType.d = 1.0;
CastFieldType klass = new CastFieldType();
klass.im = 1;
klass.bm = 1;
klass.sm = 1;
klass.cm = 1;
klass.boolm = true;
klass.lm = 1;
klass.fm = 1;
klass.dm = 1;
Integer i = klass.im;
Byte b = klass.bm;
Short s = klass.sm;
Character c = klass.cm;
Boolean bool = klass.boolm;
Long l = klass.lm;
Float f = klass.fm;
Double d = klass.dm;
int i2 = klass.bm;
int i3 = klass.sm;
int i4 = klass.cm;
}
static class CastFieldType {
static int i = 1;
static byte b = 1;
static short s = 1;
static char c = 1;
static boolean bool = true;
static long l = 1;
static float f = 1;
static double d = 1;
int im = 1;
byte bm = 1;
short sm = 1;
char cm = 1;
boolean boolm = true;
long lm = 1;
float fm = 1;
double dm = 1;
}
static void castArrayElementType() {
int[] i = new int[1];
i[0] = 1;
short[] s = new short[1];
s[0] = 1;
byte[] b = new byte[1];
b[0] = 1;
char[] c = new char[1];
c[0] = 1;
boolean[] bool = new boolean[1];
bool[0] = true;
long[] l = new long[1];
l[0] = 1;
float[] f = new float[1];
f[0] = 1;
double[] d = new double[1];
d[0] = 1;
}
static int variable() {
int i = 153;
return i;
}
static int unaryMinus() {
int i = 153;
return -i;
}
static int ifThen() {
boolean a = true;
if (a)
return 2;
return 1;
}
static int ifElse() {
boolean a = false;
if (a) {
return 2;
}
else {
return 1;
}
}
static int loop() {
int i = 0;
while (i < 10) i++;
return i;
}
static int loopWithBreak() {
int i = 0;
while (true) {
if (i > 10) break;
i++;
}
return i;
}
static int loopWithReturn() {
int i = 0;
while (true) {
if (i > 10) return i;
i++;
}
}
static int simpleFinally() {
int i = 5;
try {
return i;
}
finally {
i = 3;
}
}
static int simpleFinallyWithReturn() {
int i = 5;
try {
return i;
}
finally {
return 3;
}
}
static int simpleFinallyWithContinueInLoop() {
int i = 5;
while (true) {
try {
if (i % 2 == 0) continue;
if (i > 10) return i;
}
finally {
i++;
}
}
}
static int simpleFinallyWithBreakInLoop() {
int i = 5;
while (true) {
try {
if (i % 2 == 0) break;
}
finally {
i++;
}
}
return i;
}
static Object call() {
return Integer.valueOf(1);
}
static Object callWithObject() {
return String.valueOf("str");
}
static Object getStaticField() {
return C.FOO;
}
static int FIELD = 0;
static int putStaticField() {
FIELD = 5;
int f1 = FIELD;
FIELD = 6;
int f2 = FIELD;
return f2 + f1;
}
static class C {
static String FOO = "FOO";
int y = 15;
C(int y) {
this.y = y;
}
C() {}
int getY() {
return y;
}
static C newC() {
return new C();
}
static void throwException() {
throw new RuntimeException();
}
}
static int getInstanceField() {
return C.newC().y;
}
static int putInstanceField() {
C c = C.newC();
c.y = 5;
int f1 = c.y;
c.y = 6;
int f2 = c.y;
return f1 + f2;
}
static int instanceMethod() {
return C.newC().getY();
}
static int constructorCallNoArgs() {
return new C().y;
}
static int constructorCallWithArgs() {
return new C(10).y;
}
static class MyEx extends RuntimeException {
final int x;
MyEx(int x) {
this.x = x;
}
}
static int tryCatch() {
try {
throw new MyEx(10);
}
catch (MyEx e) {
return e.x;
}
}
static int tryWiderCatch() {
int a = 10;
try {
if (a > 0) {
throw new MyEx(10);
}
} catch (Exception e) {
return ((MyEx) e).x;
}
return 2;
}
static int classCastException() {
Object a = "";
try {
Integer s = (Integer) a;
}
catch (ClassCastException e) {
return 1;
}
return 2;
}
static String classLiteral() {
return String.class.toString();
}
static int callThrowingMethod() {
try {
C.throwException();
}
catch (RuntimeException e) {
return 1;
}
return 0;
}
static int NPE() {
try {
Object x = null;
x.toString();
}
catch (NullPointerException e) {
return 1;
}
return 0;
}
static Class<?> arrayClass() {
return int[].class;
}
static int arrayOfByte() {
byte[] a = new byte[] {1, 2, 3};
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
static int arrayOfShort() {
short[] a = new short[] {1, 2, 3};
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
static int arrayOfChar() {
char[] a = new char[] {1, 2, 3};
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
static int arrayOfInt() {
int[] a = new int[] {1, 2, 3};
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
static int arrayOfLong() {
long[] a = new long[] {1, 2, 3};
int sum = 0;
for (long i : a) {
sum += i;
}
return sum;
}
static float arrayOfFloat() {
float[] a = new float[] {1, 2, 3};
float sum = 0;
for (float i : a) {
sum += i;
}
return sum;
}
static double arrayOfDouble() {
double[] a = new double[] {1, 2, 3};
double sum = 0;
for (double i : a) {
sum += i;
}
return sum;
}
static String arrayOfString() {
String[] a = new String[] {"1", "2", "3"};
String sum = "";
for (String i : a) {
sum += i;
}
return sum;
}
static int arrayOfByte2() {
byte[][] a = new byte[][] {{1}, {2}, {3}};
int sum = 0;
for (byte[] aa: a)
for (int i : aa) {
sum += i;
}
return sum;
}
static int arrayOfShort2() {
short[][] a = new short[][] {{1}, {2}, {3}};
int sum = 0;
for (short[] aa: a)
for (int i : aa) {
sum += i;
}
return sum;
}
static int arrayOfChar2() {
char[][] a = new char[][] {{1}, {2}, {3}};
int sum = 0;
for (char[] aa: a)
for (int i : aa) {
sum += i;
}
return sum;
}
static int arrayOfInt2() {
int[][] a = new int[][] {{1}, {2}, {3}};
int sum = 0;
for (int[] aa: a)
for (int i : aa) {
sum += i;
}
return sum;
}
static int arrayOfLong2() {
long[][] a = new long[][] {{1}, {2}, {3}};
int sum = 0;
for (long[] aa: a)
for (long i : aa) {
sum += i;
}
return sum;
}
static float arrayOfFloat2() {
float[][] a = new float[][] {{1}, {2}, {3}};
float sum = 0;
for (float[] aa: a)
for (float i : aa) {
sum += i;
}
return sum;
}
static double arrayOfDouble2() {
double[][] a = new double[][] {{1}, {2}, {3}};
double sum = 0;
for (double[] aa: a)
for (double i : aa) {
sum += i;
}
return sum;
}
static String arrayOfString2() {
String[][] a = new String[][] {{"1"}, {"2"}, {"3"}};
String sum = "";
for (String[] aa: a)
for (String i : aa) {
sum += i;
}
return sum;
}
static String multiArrayOfInt() {
int[][] a = new int[2][2];
String s = "";
for (int[] x : a)
for (int y : x) {
s += y;
}
return s;
}
static String multiArrayOfString() {
String[][] a = new String[2][2];
for (String[] x : a)
for (int i = 0; i < x.length; i++) {
x[i] = i + "";
}
String s = "";
for (String[] x : a)
for (String y : x) {
s += y;
}
return s;
}
static void castToArray() {
int[] i = (int[]) null;
}
static void classNotLoadedException() {
ClassNotLoadedExceptionTest klass = new ClassNotLoadedExceptionTest();
klass.f1(null);
klass.f2(null);
klass.f3(null);
klass.f4(null, 1);
klass.f5(null, null);
klass.f6(null, null);
ClassNotLoadedExceptionTest.s1(null);
ClassNotLoadedExceptionTest.s2(null);
ClassNotLoadedExceptionTest.s3(null);
ClassNotLoadedExceptionTest.s4(null, 1);
ClassNotLoadedExceptionTest.s5(null, null);
ClassNotLoadedExceptionTest.s6(null, null);
new ClassNotLoadedExceptionTest(null, null, null);
new ClassNotLoadedExceptionTest(null, 1, null, null);
}
static class ClassNotLoadedExceptionTest {
ClassNotLoadedExceptionTest() {}
// instance methods
static class F1 {}
void f1(F1 p) {}
static class F2 {}
void f2(F2[] p) {}
static class F3 {}
void f3(F3[][] p) {}
static class F4 {}
void f4(F4[] p, int p2) {}
static class F5 {}
void f5(F5[] p, int[] p2) {}
static class F6 {}
void f6(F6[] p, int[][] p2) {}
// static methods
static class S1 {}
static void s1(S1 p) {}
static class S2 {}
static void s2(S2[] p) {}
static class S3 {}
static void s3(S3[][] p) {}
static class S4 {}
static void s4(S4[] p, int p2) {}
static class S5 {}
static void s5(S5[] p, int[] p2) {}
static class S6 {}
static void s6(S6[] p, int[][] p2) {}
// constructor
static class C2 {}
static class C1 {}
static class C3 {}
ClassNotLoadedExceptionTest(C1 p, C2[] p2, C3[][] p3) {}
static class C4 {}
ClassNotLoadedExceptionTest(C4 p, int p2, int[] p3, int[][] p4) {}
}
static void loadLibraryClasses() {
LoadLibraryClasses klass = new LoadLibraryClasses();
klass.f1(1);
klass.f2(LoadLibraryClasses.str());
klass.f3(LoadLibraryClasses.c());
klass.f4(LoadLibraryClasses.cl());
}
static class LoadLibraryClasses {
void f1(Integer i) {}
void f2(String s) {}
void f3(Class c) {}
void f4(ClassLoader cl) {}
static ClassLoader cl() { return null; }
static String str() { return null; }
static Class c() { return null; }
}
static int numberIntValue() {
Number n = 1;
return n.intValue();
}
static void getValueFromStack() {
int i = 1;
boolean b = true;
Integer[] IFEQ = new Integer[] { b ? 100 : 200 };
Integer[] IF_ICMPNE = new Integer[] { i == 1 ? 100 : 200 };
long l = 1;
Long[] IFEQ_L = new Long[] { b ? 100L : 200L };
}
@Override
String superCall() {
return "Derived";
}
@IgnoreInReflectionTests
String testInvokeSpecialForSuperCall() {
return super.superCall();
}
@IgnoreInReflectionTests
static String testInvokeSpecial() {
TestData td = new TestData();
return td.invokeSpecialPrivateFun("");
}
private String invokeSpecialPrivateFun(String s) { return "Base"; }
static Throwable exception1() {
throw new IllegalStateException();
}
static void exception2() {
new ExceptionsTest().f1();
}
static void exceptionClassCast() {
ExceptionsTest.Derived test = (ExceptionsTest.Derived) new ExceptionsTest.Base();
}
static class ExceptionsTest {
void f1() {
throw new IllegalStateException();
}
static class Base {}
static class Derived extends Base {}
}
static boolean exceptionIndexOutOfBounds() {
int[] ints = new int[1];
try { int i = ints[2]; return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { ints[2] = 1; return false; } catch (ArrayIndexOutOfBoundsException e) { }
short[] shorts = new short[1];
try { short s = shorts[2]; return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { shorts[2] = 1; return false; } catch (ArrayIndexOutOfBoundsException e) { }
char[] chars = new char[1];
try { char c = chars[2]; return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { chars[2] = 1; return false; } catch (ArrayIndexOutOfBoundsException e) { }
byte[] bytes = new byte[1];
try { byte b = bytes[2]; return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { bytes[2] = 1; return false; } catch (ArrayIndexOutOfBoundsException e) { }
long[] longs = new long[1];
try { long l = longs[2]; return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { longs[2] = 1; return false; } catch (ArrayIndexOutOfBoundsException e) { }
double[] doubles = new double[1];
try { double d = doubles[2]; return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { doubles[2] = 1.0; return false; } catch (ArrayIndexOutOfBoundsException e) { }
float[] floats = new float[1];
try { float f = floats[2]; return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { floats[2] = 1; return false; } catch (ArrayIndexOutOfBoundsException e) { }
boolean[] booleans = new boolean[1];
try { boolean bool = booleans[2];return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { booleans[2] = true; return false; } catch (ArrayIndexOutOfBoundsException e) { }
Object[] objects = new Object[1];
try { Object o = objects[2]; return false; } catch (ArrayIndexOutOfBoundsException e) { }
try { objects[2] = 1; return false; } catch (ArrayIndexOutOfBoundsException e) { }
return true;
}
static boolean indexOutOfBoundsForString() {
String str = "";
try { str.charAt(10); return false; } catch (IndexOutOfBoundsException e) { }
try { str.substring(10); return false; } catch (IndexOutOfBoundsException e) { }
return true;
}
static int coerceByte() {
byte[] b = new byte[2];
return b[1];
}
@IgnoreInReflectionTests
public static void invokeMethodWithArrayOfInterfaces() {
BaseToArray[] c = new BaseToArray[1];
TestInvokeWithObjectArray.invokeStaticFun(c);
TestInvokeWithObjectArray.invokeStaticPrivateFun(c);
TestInvokeWithObjectArray myObj = new TestInvokeWithObjectArray();
myObj.invokeMemberFun(c);
myObj.invokeMemberPrivateFun(c);
int i = TestInvokeWithObjectArray.primitiveReturnValue(c) + 1;
}
private static class TestInvokeWithObjectArray {
public static void invokeStaticFun(Object[] o) {
}
public static void invokeStaticPrivateFun(Object[] o) {
}
public void invokeMemberFun(Object[] o) {
}
public void invokeMemberPrivateFun(Object[] o) {
}
public static int primitiveReturnValue(Object[] o) {
return 1;
}
}
private interface BaseToArray {
}
public TestData() {
}
}
@@ -0,0 +1,397 @@
/*
* 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.
*/
package org.jetbrains.eval4j.test
import junit.framework.TestCase
import junit.framework.TestSuite
import org.jetbrains.eval4j.*
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_STATIC
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import java.lang.reflect.*
import java.lang.reflect.Array as JArray
fun suite(): TestSuite = buildTestSuite {
methodNode, ownerClass, expected ->
object : TestCase(getTestName(methodNode.name)) {
override fun runTest() {
if (!isIgnored(methodNode)) {
val value = interpreterLoop(
methodNode,
initFrame(
ownerClass.getInternalName(),
methodNode
),
REFLECTION_EVAL
)
if (expected is ExceptionThrown && value is ExceptionThrown) {
assertEquals(expected.exception.toString(), value.exception.toString())
}
else {
assertEquals(expected.toString(), value.toString())
}
}
}
private fun isIgnored(methodNode: MethodNode): Boolean {
return methodNode.visibleAnnotations?.any {
val annotationDesc = it.desc
annotationDesc != null &&
Type.getType(annotationDesc) == Type.getType(IgnoreInReflectionTests::class.java)
} ?: false
}
}
}
fun Class<*>.getInternalName(): String = Type.getType(this).internalName
fun initFrame(
owner: String,
m: MethodNode
): Frame<Value> {
val current = Frame<Value>(m.maxLocals, m.maxStack)
current.setReturn(makeNotInitializedValue(Type.getReturnType(m.desc)))
var local = 0
if ((m.access and ACC_STATIC) == 0) {
val ctype = Type.getObjectType(owner)
val newInstance = REFLECTION_EVAL.newInstance(ctype)
val thisValue = REFLECTION_EVAL.invokeMethod(newInstance, MethodDescription(owner, "<init>", "()V", false), listOf(), true)
current.setLocal(local++, thisValue)
}
val args = Type.getArgumentTypes(m.desc)
for (i in 0..args.size - 1) {
current.setLocal(local++, makeNotInitializedValue(args[i]))
if (args[i].size == 2) {
current.setLocal(local++, NOT_A_VALUE)
}
}
while (local < m.maxLocals) {
current.setLocal(local++, NOT_A_VALUE)
}
return current
}
fun objectToValue(obj: Any?, expectedType: Type): Value {
return when (expectedType.sort) {
Type.VOID -> VOID_VALUE
Type.BOOLEAN -> boolean(obj as Boolean)
Type.BYTE -> byte(obj as Byte)
Type.SHORT -> short(obj as Short)
Type.CHAR -> char(obj as Char)
Type.INT -> int(obj as Int)
Type.LONG -> long(obj as Long)
Type.DOUBLE -> double(obj as Double)
Type.FLOAT -> float(obj as Float)
Type.OBJECT -> if (obj == null) NULL_VALUE else ObjectValue(obj, expectedType)
else -> throw UnsupportedOperationException("Unsupported result type: $expectedType")
}
}
object REFLECTION_EVAL : Eval {
val lookup = ReflectionLookup(ReflectionLookup::class.java.classLoader!!)
override fun loadClass(classType: Type): Value {
return ObjectValue(findClass(classType), Type.getType(Class::class.java))
}
override fun loadString(str: String): Value = ObjectValue(str, Type.getType(String::class.java))
override fun newInstance(classType: Type): Value {
return NewObjectValue(classType)
}
override fun isInstanceOf(value: Value, targetType: Type): Boolean {
val _class = findClass(targetType)
return _class.isInstance(value.obj())
}
@Suppress("UNCHECKED_CAST")
override fun newArray(arrayType: Type, size: Int): Value {
return ObjectValue(JArray.newInstance(findClass(arrayType).componentType as Class<Any>, size), arrayType)
}
override fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List<Int>): Value {
return ObjectValue(ArrayHelper.newMultiArray(findClass(arrayType.elementType), *dimensionSizes.toTypedArray()), arrayType)
}
override fun getArrayLength(array: Value): Value {
return int(JArray.getLength(array.obj().checkNull()))
}
override fun getArrayElement(array: Value, index: Value): Value {
val asmType = array.asmType
val elementType = if (asmType.dimensions == 1) asmType.elementType else Type.getType(asmType.descriptor.substring(1))
val arr = array.obj().checkNull()
val ind = index.int
return mayThrow {
when (elementType.sort) {
Type.BOOLEAN -> boolean(JArray.getBoolean(arr, ind))
Type.BYTE -> byte(JArray.getByte(arr, ind))
Type.SHORT -> short(JArray.getShort(arr, ind))
Type.CHAR -> char(JArray.getChar(arr, ind))
Type.INT -> int(JArray.getInt(arr, ind))
Type.LONG -> long(JArray.getLong(arr, ind))
Type.FLOAT -> float(JArray.getFloat(arr, ind))
Type.DOUBLE -> double(JArray.getDouble(arr, ind))
Type.OBJECT,
Type.ARRAY -> {
val value = JArray.get(arr, ind)
if (value == null) NULL_VALUE else ObjectValue(value, Type.getType(value::class.java))
}
else -> throw UnsupportedOperationException("Unsupported array element type: $elementType")
}
}
}
override fun setArrayElement(array: Value, index: Value, newValue: Value) {
val arr = array.obj().checkNull()
val ind = index.int
if (array.asmType.dimensions > 1) {
JArray.set(arr, ind, newValue.obj())
return
}
val elementType = array.asmType.elementType
mayThrow {
when (elementType.sort) {
Type.BOOLEAN -> JArray.setBoolean(arr, ind, newValue.boolean)
Type.BYTE -> JArray.setByte(arr, ind, newValue.int.toByte())
Type.SHORT -> JArray.setShort(arr, ind, newValue.int.toShort())
Type.CHAR -> JArray.setChar(arr, ind, newValue.int.toChar())
Type.INT -> JArray.setInt(arr, ind, newValue.int)
Type.LONG -> JArray.setLong(arr, ind, newValue.long)
Type.FLOAT -> JArray.setFloat(arr, ind, newValue.float)
Type.DOUBLE -> JArray.setDouble(arr, ind, newValue.double)
Type.OBJECT,
Type.ARRAY -> {
JArray.set(arr, ind, newValue.obj())
}
else -> throw UnsupportedOperationException("Unsupported array element type: $elementType")
}
}
}
fun <T> mayThrow(f: () -> T): T {
try {
try {
return f()
}
catch (ite: InvocationTargetException) {
throw ite.cause ?: ite
}
}
catch (e: Throwable) {
throw ThrownFromEvaluatedCodeException(ObjectValue(e, Type.getType(e::class.java)))
}
}
override fun getStaticField(fieldDesc: FieldDescription): Value {
val field = findStaticField(fieldDesc)
val result = mayThrow {field.get(null)}
return objectToValue(result, fieldDesc.fieldType)
}
override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) {
val field = findStaticField(fieldDesc)
val obj = newValue.obj(fieldDesc.fieldType)
mayThrow {field.set(null, obj)}
}
fun findStaticField(fieldDesc: FieldDescription): Field {
assertTrue(fieldDesc.isStatic)
val field = findClass(fieldDesc).findField(fieldDesc)
assertNotNull("Field not found: $fieldDesc", field)
assertTrue("Field is not static: $field", (field!!.modifiers and Modifier.STATIC) != 0)
return field
}
override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List<Value>): Value {
assertTrue(methodDesc.isStatic)
val method = findClass(methodDesc).findMethod(methodDesc)
assertNotNull("Method not found: $methodDesc", method)
val args = mapArguments(arguments, methodDesc.parameterTypes).toTypedArray()
val result = mayThrow {method!!.invoke(null, *args)}
return objectToValue(result, methodDesc.returnType)
}
fun findClass(memberDesc: MemberDescription): Class<Any> = findClass(Type.getObjectType(memberDesc.ownerInternalName))
fun findClass(asmType: Type): Class<Any> {
val owner = lookup.findClass(asmType)
assertNotNull("Class not found: ${asmType}", owner)
return owner as Class<Any>
}
override fun getField(instance: Value, fieldDesc: FieldDescription): Value {
val obj = instance.obj().checkNull()
val field = findInstanceField(obj, fieldDesc)
return objectToValue(mayThrow {field.get(obj)}, fieldDesc.fieldType)
}
override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) {
val obj = instance.obj().checkNull()
val field = findInstanceField(obj, fieldDesc)
val newObj = newValue.obj(fieldDesc.fieldType)
mayThrow {field.set(obj, newObj)}
}
fun findInstanceField(obj: Any, fieldDesc: FieldDescription): Field {
val _class = obj::class.java
val field = _class.findField(fieldDesc)
assertNotNull("Field not found: $fieldDesc", field)
return field!!
}
override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List<Value>, invokespecial: Boolean): Value {
if (invokespecial) {
if (methodDesc.name == "<init>") {
// Constructor call
@Suppress("UNCHECKED_CAST")
val _class = findClass((instance as NewObjectValue).asmType)
val ctor = _class.findConstructor(methodDesc)
assertNotNull("Constructor not found: $methodDesc", ctor)
val args = mapArguments(arguments, methodDesc.parameterTypes).toTypedArray()
val result = mayThrow {ctor!!.newInstance(*args)}
instance.value = result
return objectToValue(result, instance.asmType)
}
else {
// TODO
throw UnsupportedOperationException("invokespecial is not suported in reflection eval")
}
}
val obj = instance.obj().checkNull()
val method = obj::class.java.findMethod(methodDesc)
assertNotNull("Method not found: $methodDesc", method)
val args = mapArguments(arguments, methodDesc.parameterTypes).toTypedArray()
val result = mayThrow {method!!.invoke(obj, *args)}
return objectToValue(result, methodDesc.returnType)
}
private fun mapArguments(arguments: List<Value>, expecetedTypes: List<Type>): List<Any?> {
return arguments.zip(expecetedTypes).map {
val (arg, expectedType) = it
arg.obj(expectedType)
}
}
}
class ReflectionLookup(val classLoader: ClassLoader) {
@Suppress("UNCHECKED_CAST")
fun findClass(asmType: Type): Class<*>? {
return when (asmType.sort) {
Type.BOOLEAN -> java.lang.Boolean.TYPE
Type.BYTE -> java.lang.Byte.TYPE
Type.SHORT -> java.lang.Short.TYPE
Type.CHAR -> java.lang.Character.TYPE
Type.INT -> java.lang.Integer.TYPE
Type.LONG -> java.lang.Long.TYPE
Type.FLOAT -> java.lang.Float.TYPE
Type.DOUBLE -> java.lang.Double.TYPE
Type.OBJECT -> classLoader.loadClass(asmType.internalName.replace('/', '.'))
Type.ARRAY -> Class.forName(asmType.descriptor.replace('/', '.'))
else -> throw UnsupportedOperationException("Unsupported type: $asmType")
}
}
}
@Suppress("UNCHECKED_CAST")
fun Class<out Any>.findMethod(methodDesc: MethodDescription): Method? {
for (declared in declaredMethods) {
if (methodDesc.matches(declared)) return declared
}
val fromSuperClass = (superclass as Class<Any>).findMethod(methodDesc)
if (fromSuperClass != null) return fromSuperClass
for (supertype in interfaces) {
val fromSuper = (supertype as Class<Any>).findMethod(methodDesc)
if (fromSuper != null) return fromSuper
}
return null
}
@Suppress("UNCHECKED_CAST")
fun Class<Any>.findConstructor(methodDesc: MethodDescription): Constructor<Any?>? {
for (declared in declaredConstructors) {
if (methodDesc.matches(declared)) return declared as Constructor<Any?>
}
return null
}
fun MethodDescription.matches(ctor: Constructor<*>): Boolean {
val methodParams = ctor.parameterTypes!!
if (parameterTypes.size != methodParams.size) return false
for ((i, p) in parameterTypes.withIndex()) {
if (!p.matches(methodParams[i])) return false
}
return true
}
fun MethodDescription.matches(method: Method): Boolean {
if (name != method.name) return false
val methodParams = method.parameterTypes!!
if (parameterTypes.size != methodParams.size) return false
for ((i, p) in parameterTypes.withIndex()) {
if (!p.matches(methodParams[i])) return false
}
return returnType.matches(method.returnType!!)
}
@Suppress("UNCHECKED_CAST")
fun Class<out Any>.findField(fieldDesc: FieldDescription): Field? {
for (declared in declaredFields) {
if (fieldDesc.matches(declared)) return declared
}
val superclass = superclass
if (superclass != null) {
val fromSuperClass = (superclass as Class<Any>).findField(fieldDesc)
if (fromSuperClass != null) return fromSuperClass
}
for (supertype in interfaces) {
val fromSuper = (supertype as Class<Any>).findField(fieldDesc)
if (fromSuper != null) return fromSuper
}
return null
}
fun FieldDescription.matches(field: Field): Boolean {
if (name != field.name) return false
return fieldType.matches(field.type!!)
}
fun Type.matches(_class: Class<*>): Boolean = this == Type.getType(_class)
@@ -0,0 +1,95 @@
/*
* 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.
*/
package org.jetbrains.eval4j.test
import junit.framework.TestCase
import junit.framework.TestSuite
import org.jetbrains.eval4j.ExceptionThrown
import org.jetbrains.eval4j.InterpreterResult
import org.jetbrains.eval4j.ObjectValue
import org.jetbrains.eval4j.ValueReturned
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes.API_VERSION
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.lang.reflect.Modifier
import java.lang.reflect.Array as JArray
fun buildTestSuite(
create: (MethodNode, Class<*>, InterpreterResult?) -> TestCase
): TestSuite {
val suite = TestSuite()
val ownerClass = TestData::class.java
ownerClass.classLoader!!.getResourceAsStream(ownerClass.getInternalName() + ".class")!!.use { inputStream ->
ClassReader(inputStream).accept(object : ClassVisitor(API_VERSION) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
return object : MethodNode(API_VERSION, access, name, desc, signature, exceptions) {
override fun visitEnd() {
val testCase = buildTestCase(ownerClass, this, create)
if (testCase != null) {
suite.addTest(testCase)
}
}
}
}
}, 0)
}
return suite
}
private fun buildTestCase(ownerClass: Class<TestData>,
methodNode: MethodNode,
create: (MethodNode, Class<out Any?>, InterpreterResult?) -> TestCase): TestCase? {
var expected: InterpreterResult? = null
for (method in ownerClass.declaredMethods) {
if (method.name == methodNode.name) {
val isStatic = (method.modifiers and Modifier.STATIC) != 0
if (method.parameterTypes!!.size > 0) {
println("Skipping method with parameters: $method")
}
else if (!isStatic && !method.name!!.startsWith("test")) {
println("Skipping instance method (should be started with 'test') : $method")
}
else {
method.isAccessible = true
try {
val result = method.invoke(if (isStatic) null else ownerClass.newInstance())
val returnType = Type.getType(method.returnType!!)
expected = ValueReturned(objectToValue(result, returnType))
}
catch (e: UnsupportedOperationException) {
println("Skipping $method: $e")
}
catch (e: Throwable) {
val cause = e.cause ?: e
expected = ExceptionThrown(objectToValue(cause, Type.getType(cause::class.java)) as ObjectValue, ExceptionThrown.ExceptionKind.FROM_EVALUATOR)
}
}
}
}
if (expected == null) {
println("Method not found: ${methodNode.name}")
return null
}
return create(methodNode, ownerClass, expected)
}
@@ -0,0 +1,19 @@
/*
* 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.
*/
package org.jetbrains.eval4j.test
fun getTestName(methodName: String) = if (methodName.startsWith("test")) methodName else "test${methodName.capitalize()}"