Move out JVM debugger functionality
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
eval4j is a Java byte code interpreter written in Kotlin.
|
||||
Its primary use case is implementing expression evaluation in debuggers.
|
||||
@@ -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()}"
|
||||
Reference in New Issue
Block a user