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()}"
|
||||
@@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile(project(":compiler:backend"))
|
||||
compile(project(":compiler:backend-common"))
|
||||
compile(project(":compiler:light-classes"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:jvm-debugger:jvm-debugger-util"))
|
||||
compile(files("${System.getProperty("java.home")}/../lib/tools.jar"))
|
||||
|
||||
compileOnly(intellijPluginDep("stream-debugger"))
|
||||
|
||||
testCompile(project(":kotlin-test:kotlin-test-junit"))
|
||||
testCompile(commonDep("junit:junit"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { none() }
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.engine.DebuggerUtils
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ObjectCollectedException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClassOrNull
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.getOrComputeClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.Cached
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.EMPTY
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.NonCached
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import java.util.*
|
||||
|
||||
class DebuggerClassNameProvider(
|
||||
private val debugProcess: DebugProcess,
|
||||
val findInlineUseSites: Boolean = true,
|
||||
val alwaysReturnLambdaParentClass: Boolean = true
|
||||
) {
|
||||
companion object {
|
||||
private val CLASS_ELEMENT_TYPES = arrayOf<Class<out PsiElement>>(
|
||||
KtFile::class.java,
|
||||
KtClassOrObject::class.java,
|
||||
KtProperty::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtFunctionLiteral::class.java,
|
||||
KtAnonymousInitializer::class.java
|
||||
)
|
||||
|
||||
internal fun getRelevantElement(element: PsiElement?): PsiElement? {
|
||||
if (element == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (elementType in CLASS_ELEMENT_TYPES) {
|
||||
if (elementType.isInstance(element)) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
|
||||
// Do not copy the array (*elementTypes) if the element is one we look for
|
||||
return runReadAction { PsiTreeUtil.getNonStrictParentOfType(element, *CLASS_ELEMENT_TYPES) }
|
||||
}
|
||||
}
|
||||
|
||||
private val inlineUsagesSearcher = InlineCallableUsagesSearcher(debugProcess)
|
||||
|
||||
/**
|
||||
* Returns classes in which the given line number *is* present.
|
||||
*/
|
||||
fun getClassesForPosition(position: SourcePosition): List<ReferenceType> = with(debugProcess) {
|
||||
val lineNumber = runReadAction { position.line }
|
||||
|
||||
return doGetClassesForPosition(position)
|
||||
.flatMap { className -> virtualMachineProxy.classesByName(className) }
|
||||
.flatMap { referenceType -> findTargetClasses(referenceType, lineNumber) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns classes names in JDI format (my.app.App$Nested) in which the given line number *may be* present.
|
||||
*/
|
||||
fun getOuterClassNamesForPosition(position: SourcePosition): List<String> {
|
||||
return doGetClassesForPosition(position).toList()
|
||||
}
|
||||
|
||||
private fun doGetClassesForPosition(position: SourcePosition): Set<String> {
|
||||
val relevantElement = runReadAction {
|
||||
position.elementAt?.let { getRelevantElement(it) }
|
||||
}
|
||||
|
||||
val result = getOrComputeClassNames(relevantElement) { element ->
|
||||
getOuterClassNamesForElement(element)
|
||||
}.toMutableSet()
|
||||
|
||||
for (lambda in position.readAction(::getLambdasAtLineIfAny)) {
|
||||
result += getOrComputeClassNames(lambda) { element ->
|
||||
getOuterClassNamesForElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@Suppress("NON_TAIL_RECURSIVE_CALL")
|
||||
internal tailrec fun getOuterClassNamesForElement(element: PsiElement?): ComputedClassNames {
|
||||
if (element == null) return EMPTY
|
||||
|
||||
return when (element) {
|
||||
is KtScript -> {
|
||||
getClassType(element)?.let { return Cached(it) }
|
||||
return EMPTY
|
||||
}
|
||||
is KtFile -> {
|
||||
val fileClassName = runReadAction { JvmFileClassUtil.getFileClassInternalName(element) }.toJdiName()
|
||||
Cached(fileClassName)
|
||||
}
|
||||
is KtClassOrObject -> {
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
when {
|
||||
enclosingElementForLocal != null ->
|
||||
// A local class
|
||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
runReadAction { element.isObjectLiteral() } ->
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
else ->
|
||||
// Guaranteed to be non-local class or object
|
||||
element.readAction { _ ->
|
||||
if (element is KtClass && runReadAction { element.isInterface() }) {
|
||||
val name = getNameForNonLocalClass(element)
|
||||
|
||||
if (name != null)
|
||||
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
|
||||
else
|
||||
EMPTY
|
||||
} else {
|
||||
getNameForNonLocalClass(element)?.let { Cached(it) } ?: EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is KtProperty -> {
|
||||
val nonInlineClasses = if (runReadAction { element.isTopLevel }) {
|
||||
// Top level property
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
} else {
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
if (enclosingElementForLocal != null) {
|
||||
// Local class
|
||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
} else {
|
||||
val containingClassOrFile = runReadAction {
|
||||
PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java)
|
||||
}
|
||||
|
||||
if (containingClassOrFile is KtObjectDeclaration && containingClassOrFile.isCompanionInReadAction) {
|
||||
// Properties from the companion object can be placed in the companion object's containing class
|
||||
(getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction) +
|
||||
getOuterClassNamesForElement(containingClassOrFile)).distinct()
|
||||
} else if (containingClassOrFile != null) {
|
||||
getOuterClassNamesForElement(containingClassOrFile)
|
||||
} else {
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (findInlineUseSites && (
|
||||
element.isInlineInReadAction ||
|
||||
runReadAction { element.accessors.any { it.hasModifier(KtTokens.INLINE_KEYWORD) } })
|
||||
) {
|
||||
nonInlineClasses + inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
||||
} else {
|
||||
return NonCached(nonInlineClasses.classNames)
|
||||
}
|
||||
}
|
||||
is KtNamedFunction -> {
|
||||
val classNamesOfContainingDeclaration = getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
|
||||
var nonInlineClasses: ComputedClassNames = classNamesOfContainingDeclaration
|
||||
|
||||
if (runReadAction { element.name == null || element.isLocal }) {
|
||||
val nameOfAnonymousClass = runReadAction { getClassType(element) }
|
||||
if (nameOfAnonymousClass != null) {
|
||||
nonInlineClasses += Cached(nameOfAnonymousClass)
|
||||
}
|
||||
}
|
||||
|
||||
if (!findInlineUseSites || !element.isInlineInReadAction) {
|
||||
return NonCached(nonInlineClasses.classNames)
|
||||
}
|
||||
|
||||
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
||||
|
||||
nonInlineClasses + inlineCallSiteClasses
|
||||
}
|
||||
is KtAnonymousInitializer -> {
|
||||
val initializerOwner = runReadAction { element.containingDeclaration }
|
||||
|
||||
if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) {
|
||||
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
|
||||
}
|
||||
|
||||
getOuterClassNamesForElement(initializerOwner)
|
||||
}
|
||||
is KtFunctionLiteral -> {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
|
||||
val nonInlinedLambdaClassName = runReadAction {
|
||||
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName()
|
||||
}
|
||||
|
||||
if (!alwaysReturnLambdaParentClass && !InlineUtil.isInlinedArgument(element, typeMapper.bindingContext, true)) {
|
||||
return Cached(nonInlinedLambdaClassName)
|
||||
}
|
||||
|
||||
Cached(nonInlinedLambdaClassName) + getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
else -> getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
}
|
||||
|
||||
// Should be called in a read action
|
||||
private fun getClassType(element: KtElement): String? {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
asmTypeForAnonymousClassOrNull(typeMapper.bindingContext, element)?.let { return it.className }
|
||||
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, element]
|
||||
if (descriptor is ScriptDescriptor) {
|
||||
return typeMapper.mapClass(descriptor).className
|
||||
}
|
||||
|
||||
if (descriptor != null) {
|
||||
val containingDeclaration = descriptor.containingDeclaration
|
||||
if (containingDeclaration is ScriptDescriptor) {
|
||||
return typeMapper.mapClass(containingDeclaration).className
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getNameForNonLocalClass(nonLocalClassOrObject: KtClassOrObject): String? {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(nonLocalClassOrObject)
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.CLASS, nonLocalClassOrObject] ?: return null
|
||||
|
||||
val type = typeMapper.mapClass(descriptor)
|
||||
if (type.sort != Type.OBJECT) {
|
||||
return null
|
||||
}
|
||||
|
||||
return type.className
|
||||
}
|
||||
|
||||
private val KtDeclaration.isInlineInReadAction: Boolean
|
||||
get() = runReadAction { hasModifier(KtTokens.INLINE_KEYWORD) }
|
||||
|
||||
private val KtObjectDeclaration.isCompanionInReadAction: Boolean
|
||||
get() = runReadAction { isCompanion() }
|
||||
|
||||
private val PsiElement.relevantParentInReadAction
|
||||
get() = runReadAction { getRelevantElement(this.parent) }
|
||||
}
|
||||
|
||||
private fun String.toJdiName() = replace('/', '.')
|
||||
|
||||
private fun DebugProcess.findTargetClasses(outerClass: ReferenceType, lineAt: Int): List<ReferenceType> {
|
||||
val vmProxy = virtualMachineProxy
|
||||
|
||||
try {
|
||||
if (!outerClass.isPrepared) {
|
||||
return emptyList()
|
||||
}
|
||||
} catch (e: ObjectCollectedException) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val targetClasses = ArrayList<ReferenceType>(1)
|
||||
|
||||
try {
|
||||
for (location in outerClass.allLineLocations()) {
|
||||
val locationLine = location.lineNumber() - 1
|
||||
if (locationLine < 0) {
|
||||
// such locations are not correspond to real lines in code
|
||||
continue
|
||||
}
|
||||
|
||||
if (lineAt == locationLine) {
|
||||
val method = location.method()
|
||||
if (method == null || DebuggerUtils.isSynthetic(method) || method.isBridge) {
|
||||
// skip synthetic methods
|
||||
continue
|
||||
}
|
||||
|
||||
targetClasses += outerClass
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The same line number may appear in different classes so we have to scan nested classes as well.
|
||||
// For example, in the next example line 3 appears in both Foo and Foo$Companion.
|
||||
|
||||
/* class Foo {
|
||||
companion object {
|
||||
val a = Foo() /* line 3 */
|
||||
}
|
||||
} */
|
||||
|
||||
val nestedTypes = vmProxy.nestedTypes(outerClass)
|
||||
for (nested in nestedTypes) {
|
||||
targetClasses += findTargetClasses(nested, lineAt)
|
||||
}
|
||||
} catch (_: AbsentInformationException) {
|
||||
}
|
||||
|
||||
return targetClasses
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
abstract class DelegateSourcePosition(private var delegate: SourcePosition) : SourcePosition() {
|
||||
override fun getFile(): PsiFile = delegate.file
|
||||
override fun getElementAt(): PsiElement? = delegate.elementAt
|
||||
override fun getLine(): Int = delegate.line
|
||||
override fun getOffset(): Int = delegate.offset
|
||||
|
||||
override fun openEditor(requestFocus: Boolean): Editor = delegate.openEditor(requestFocus)
|
||||
|
||||
override fun canNavigate() = delegate.canNavigate()
|
||||
override fun canNavigateToSource() = delegate.canNavigateToSource()
|
||||
|
||||
override fun navigate(requestFocus: Boolean) {
|
||||
delegate.navigate(requestFocus)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = delegate.hashCode()
|
||||
override fun equals(other: Any?) = delegate == other
|
||||
|
||||
override fun toString() = "DSP($delegate)"
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticSuppressor
|
||||
|
||||
class DiagnosticSuppressorForDebugger : DiagnosticSuppressor {
|
||||
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
||||
val element = diagnostic.psiElement
|
||||
val containingFile = element.containingFile
|
||||
|
||||
if (containingFile is KtCodeFragment) {
|
||||
val diagnosticFactory = diagnostic.factory
|
||||
return diagnosticFactory == Errors.UNSAFE_CALL
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerClassNameProvider.Companion.getRelevantElement
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isInsideOf
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
|
||||
class InlineCallableUsagesSearcher(private val myDebugProcess: DebugProcess) {
|
||||
fun findInlinedCalls(
|
||||
declaration: KtDeclaration,
|
||||
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(declaration).bindingContext,
|
||||
transformer: (PsiElement) -> ComputedClassNames
|
||||
): ComputedClassNames {
|
||||
if (!checkIfInline(declaration, bindingContext)) {
|
||||
return ComputedClassNames.EMPTY
|
||||
}
|
||||
else {
|
||||
val searchResult = hashSetOf<PsiElement>()
|
||||
val declarationName = runReadAction { declaration.name }
|
||||
|
||||
val task = Runnable {
|
||||
ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration)).forEach {
|
||||
if (!runReadAction { it.isImportUsage() }) {
|
||||
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
|
||||
if (usage != null && !runReadAction { declaration.isAncestor(usage) }) {
|
||||
searchResult.add(usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSuccess = true
|
||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||
if (applicationEx.isDispatchThread) {
|
||||
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
task,
|
||||
"Compute class names for declaration $declarationName",
|
||||
true,
|
||||
myDebugProcess.project)
|
||||
}
|
||||
else {
|
||||
ProgressManager.getInstance().runProcess(task, EmptyProgressIndicator())
|
||||
}
|
||||
|
||||
if (!isSuccess) {
|
||||
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
||||
"Debugger can skip some executions of $declarationName because the computation of class names was interrupted",
|
||||
MessageType.WARNING
|
||||
).notify(myDebugProcess.project)
|
||||
}
|
||||
|
||||
val results = searchResult.map { transformer(it) }
|
||||
return ComputedClassNames(results.flatMap { it.classNames }, shouldBeCached = results.all { it.shouldBeCached })
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfInline(declaration: KtDeclaration, bindingContext: BindingContext): Boolean {
|
||||
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) ?: return false
|
||||
return when (descriptor) {
|
||||
is FunctionDescriptor -> InlineUtil.isInline(descriptor)
|
||||
is PropertyDescriptor -> InlineUtil.hasInlineAccessors(descriptor)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScopeForInlineDeclarationUsages(inlineDeclaration: KtDeclaration): GlobalSearchScope {
|
||||
val virtualFile = runReadAction { inlineDeclaration.containingFile.virtualFile }
|
||||
return if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
||||
myDebugProcess.searchScope.uniteWith(
|
||||
KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project))
|
||||
}
|
||||
else {
|
||||
myDebugProcess.searchScope
|
||||
}
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.ide.util.ModuleRendererFactory
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.ui.EditorNotificationPanel
|
||||
import com.intellij.ui.EditorNotifications
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil.findFilesWithExactPackage
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class KotlinAlternativeSourceNotificationProvider(private val myProject: Project) : EditorNotifications.Provider<EditorNotificationPanel>() {
|
||||
override fun getKey(): Key<EditorNotificationPanel> {
|
||||
return KEY
|
||||
}
|
||||
|
||||
override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? {
|
||||
if (!DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE) {
|
||||
return null
|
||||
}
|
||||
|
||||
val session = XDebuggerManager.getInstance(myProject).currentSession
|
||||
if (session == null) {
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
val position = session.currentPosition
|
||||
if (file != position?.file) {
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
if (DumbService.getInstance(myProject).isDumb) return null
|
||||
|
||||
val ktFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile ?: return null
|
||||
|
||||
val packageFqName = ktFile.packageFqName
|
||||
val fileName = ktFile.name
|
||||
|
||||
val alternativeKtFiles = findFilesWithExactPackage(packageFqName, GlobalSearchScope.allScope(myProject), myProject).filterTo(HashSet()) {
|
||||
it.name == fileName
|
||||
}
|
||||
|
||||
FILE_PROCESSED_KEY.set(file, true)
|
||||
|
||||
if (alternativeKtFiles.size <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
val currentFirstAlternatives: Collection<KtFile> = listOf(ktFile) + alternativeKtFiles.filter { it != ktFile }
|
||||
|
||||
val frame = session.currentStackFrame
|
||||
val locationDeclName: String? = when (frame) {
|
||||
is JavaStackFrame -> {
|
||||
val location = frame.descriptor.location
|
||||
location?.declaringType()?.name()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
return AlternativeSourceNotificationPanel(currentFirstAlternatives, myProject, file, locationDeclName)
|
||||
}
|
||||
|
||||
private class AlternativeSourceNotificationPanel(
|
||||
alternatives: Collection<KtFile>,
|
||||
project: Project,
|
||||
file: VirtualFile,
|
||||
locationDeclName: String?
|
||||
) : EditorNotificationPanel() {
|
||||
private class ComboBoxFileElement(val ktFile: KtFile) {
|
||||
private val label: String by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val factory = ModuleRendererFactory.findInstance(ktFile)
|
||||
val moduleRenderer = factory.moduleRenderer
|
||||
moduleRenderer.getListCellRendererComponent(ourDummyList, ktFile, 1, false, false)
|
||||
moduleRenderer.text ?: ""
|
||||
}
|
||||
|
||||
override fun toString(): String = label
|
||||
|
||||
companion object {
|
||||
private val ourDummyList = JBList<KtFile>()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setText("Alternative source available for file ${file.name}")
|
||||
|
||||
val items = alternatives.map { ComboBoxFileElement(it) }
|
||||
myLinksPanel.add(
|
||||
ComboBox<ComboBoxFileElement>(items.toTypedArray()).apply {
|
||||
addActionListener {
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val session = context.debuggerSession
|
||||
val ktFile = (selectedItem as ComboBoxFileElement).ktFile
|
||||
val vFile = ktFile.containingFile.virtualFile
|
||||
|
||||
when {
|
||||
session != null && vFile != null ->
|
||||
session.process.managerThread.schedule(object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
if (!StringUtil.isEmpty(locationDeclName)) {
|
||||
DebuggerUtilsEx.setAlternativeSourceUrl(locationDeclName, vFile.url, project)
|
||||
}
|
||||
|
||||
DebuggerUIUtil.invokeLater {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
session.refresh(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
else -> {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
ktFile.navigate(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
createActionLabel("Disable") {
|
||||
DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE = false
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
val fileEditorManager = FileEditorManager.getInstance(project)
|
||||
val editor = fileEditorManager.getSelectedEditor(file)
|
||||
if (editor != null) {
|
||||
fileEditorManager.removeTopComponent(editor, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY = Key.create<EditorNotificationPanel>("KotlinAlternativeSource")
|
||||
|
||||
// FIXME: Share AlternativeSourceNotificationProvider.FILE_PROCESSED_KEY
|
||||
@Suppress("UNCHECKED_CAST", "DEPRECATION")
|
||||
private val FILE_PROCESSED_KEY = Key.findKeyByName("AlternativeSourceCheckDone") as Key<Boolean>
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.GeneratedLocation
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||
import com.intellij.xdebugger.frame.XNamedValue
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||
private companion object {
|
||||
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
||||
|
||||
tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
||||
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
||||
return type
|
||||
}
|
||||
|
||||
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||
return getAsyncStackTrace(stackFrame.stackFrameProxy, suspendContext)
|
||||
}
|
||||
|
||||
fun getAsyncStackTrace(frameProxy: StackFrameProxyImpl, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||
val location = frameProxy.location()
|
||||
if (!location.isInKotlinSources()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val method = location.safeMethod() ?: return null
|
||||
val threadReference = frameProxy.threadProxy().threadReference
|
||||
|
||||
if (threadReference == null || !threadReference.isSuspended || !suspendContext.debugProcess.canRunEvaluation) {
|
||||
return null
|
||||
}
|
||||
|
||||
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
|
||||
val context = ExecutionContext(evaluationContext, frameProxy)
|
||||
|
||||
// DebugMetadataKt not found, probably old kotlin-stdlib version
|
||||
val debugMetadataKtType = context.findClassSafe(DEBUG_METADATA_KT) ?: return null
|
||||
|
||||
val asyncContext = AsyncStackTraceContext(context, method, debugMetadataKtType)
|
||||
return asyncContext.getAsyncStackTraceForSuspendLambda() ?: asyncContext.getAsyncStackTraceForSuspendFunction()
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendLambda(): List<StackFrameItem>? {
|
||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||
return null
|
||||
}
|
||||
|
||||
val thisObject = context.frameProxy.thisObject() ?: return null
|
||||
val thisType = thisObject.referenceType()
|
||||
|
||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return collectFrames(thisObject)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
||||
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val frameProxy = context.frameProxy
|
||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||
|
||||
return collectFrames(continuation)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
||||
val frames = mutableListOf<StackFrameItem>()
|
||||
collectFramesRecursively(continuation, frames)
|
||||
return frames
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
||||
val continuationType = continuation.referenceType() as? ClassType ?: return
|
||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
||||
|
||||
val location = getLocation(continuation)
|
||||
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
||||
|
||||
if (location != null) {
|
||||
consumer += StackFrameItem(location, spilledVariables)
|
||||
}
|
||||
|
||||
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
|
||||
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
|
||||
collectFramesRecursively(completion, consumer)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getLocation(continuation: ObjectReference): Location? {
|
||||
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
||||
"getStackTraceElement",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
||||
?: return null
|
||||
|
||||
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
||||
?: return null
|
||||
|
||||
fun getValue(name: String, desc: String): Value? {
|
||||
val method = stackTraceElementType.methodsByName(name, desc).single()
|
||||
return context.invokeMethod(stackTraceElement, method, emptyList())
|
||||
}
|
||||
|
||||
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
||||
|
||||
val locationClass = context.findClassSafe(className) ?: return null
|
||||
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
||||
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
||||
"getSpilledVariableFieldMapping",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
||||
?: return null
|
||||
|
||||
val length = rawSpilledVariables.length() / 2
|
||||
val spilledVariables = ArrayList<XNamedValue>(length)
|
||||
|
||||
for (index in 0 until length) {
|
||||
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
||||
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
||||
|
||||
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
||||
override fun calcValueName() = variableName
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||
throw EvaluateException("Spilled variable evaluation is not supported")
|
||||
}
|
||||
|
||||
spilledVariables += JavaValue.create(
|
||||
null,
|
||||
valueDescriptor,
|
||||
context.evaluationContext,
|
||||
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
return spilledVariables
|
||||
}
|
||||
|
||||
private fun ExecutionContext.findClassSafe(className: String): ClassType? {
|
||||
return try {
|
||||
findClass(className) as? ClassType
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncStackTraceContext(
|
||||
val context: ExecutionContext,
|
||||
val method: Method,
|
||||
val debugMetadataKtType: ClassType
|
||||
)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
|
||||
interface KotlinCoroutinesAsyncStackTraceProviderBase : AsyncStackTraceProvider {
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>?
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
|
||||
interface KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>?
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import com.intellij.openapi.options.SimpleConfigurable
|
||||
import com.intellij.openapi.util.Getter
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.settings.DebuggerSettingsCategory
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettings
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingConfigurableUi
|
||||
|
||||
@State(name = "KotlinDebuggerSettings", storages = arrayOf(Storage("kotlin_debug.xml")))
|
||||
class KotlinDebuggerSettings : XDebuggerSettings<KotlinDebuggerSettings>("kotlin_debugger"), Getter<KotlinDebuggerSettings> {
|
||||
var DEBUG_RENDER_DELEGATED_PROPERTIES: Boolean = true
|
||||
var DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES: Boolean = true
|
||||
var DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED: Boolean = false
|
||||
|
||||
companion object {
|
||||
fun getInstance(): KotlinDebuggerSettings {
|
||||
return XDebuggerUtil.getInstance()?.getDebuggerSettings(KotlinDebuggerSettings::class.java)!!
|
||||
}
|
||||
}
|
||||
|
||||
override fun createConfigurables(category: DebuggerSettingsCategory): Collection<Configurable?> {
|
||||
return when (category) {
|
||||
DebuggerSettingsCategory.STEPPING ->
|
||||
listOf(SimpleConfigurable.create(
|
||||
"reference.idesettings.debugger.kotlin.stepping",
|
||||
"Kotlin",
|
||||
KotlinSteppingConfigurableUi::class.java,
|
||||
this))
|
||||
DebuggerSettingsCategory.DATA_VIEWS ->
|
||||
listOf(SimpleConfigurable.create(
|
||||
"reference.idesettings.debugger.kotlin.data.view",
|
||||
"Kotlin",
|
||||
KotlinDelegatedPropertyRendererConfigurableUi::class.java,
|
||||
this))
|
||||
else -> listOf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getState() = this
|
||||
override fun get() = this
|
||||
|
||||
override fun loadState(state: KotlinDebuggerSettings) {
|
||||
XmlSerializerUtil.copyBean<KotlinDebuggerSettings>(state, this)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.KotlinDelegatedPropertyRendererConfigurableUi">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="99d36" class="javax.swing.JCheckBox" binding="renderDelegatedProperties">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="false"/>
|
||||
<text resource-bundle="org/jetbrains/kotlin/idea/KotlinBundle" key="debugger.data.view.delegated.properties"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="c37da">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger;
|
||||
|
||||
|
||||
import com.intellij.openapi.options.ConfigurableUi;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class KotlinDelegatedPropertyRendererConfigurableUi implements ConfigurableUi<KotlinDebuggerSettings> {
|
||||
private JCheckBox renderDelegatedProperties;
|
||||
private JPanel myPanel;
|
||||
|
||||
@Override
|
||||
public void reset(@NotNull KotlinDebuggerSettings settings) {
|
||||
boolean flag = settings.getDEBUG_RENDER_DELEGATED_PROPERTIES();
|
||||
renderDelegatedProperties.setSelected(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified(@NotNull KotlinDebuggerSettings settings) {
|
||||
return settings.getDEBUG_RENDER_DELEGATED_PROPERTIES() != renderDelegatedProperties.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(@NotNull KotlinDebuggerSettings settings) {
|
||||
settings.setDEBUG_RENDER_DELEGATED_PROPERTIES(renderDelegatedProperties.isSelected());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myPanel;
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.FrameExtraVariablesProvider
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.text.CharArrayUtil
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineEndOffset
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class KotlinFrameExtraVariablesProvider : FrameExtraVariablesProvider {
|
||||
override fun isAvailable(sourcePosition: SourcePosition, evalContext: EvaluationContext): Boolean {
|
||||
if (runReadAction { sourcePosition.line } < 0) return false
|
||||
return sourcePosition.file.fileType == KotlinFileType.INSTANCE && DebuggerSettings.getInstance().AUTO_VARIABLES_MODE
|
||||
}
|
||||
|
||||
override fun collectVariables(
|
||||
sourcePosition: SourcePosition, evalContext: EvaluationContext, alreadyCollected: MutableSet<String>): Set<TextWithImports> {
|
||||
return runReadAction { findAdditionalExpressions(sourcePosition) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun findAdditionalExpressions(position: SourcePosition): Set<TextWithImports> {
|
||||
val line = position.line
|
||||
val file = position.file
|
||||
|
||||
val vFile = file.virtualFile
|
||||
val doc = if (vFile != null) FileDocumentManager.getInstance().getDocument(vFile) else null
|
||||
if (doc == null || doc.lineCount == 0 || line > (doc.lineCount - 1)) {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
val offset = file.getLineStartOffset(line)?.takeIf { it > 0 } ?: return emptySet()
|
||||
|
||||
val elem = file.findElementAt(offset) ?: return emptySet()
|
||||
val containingElement = getContainingElement(elem) ?: elem
|
||||
|
||||
val limit = getLineRangeForElement(containingElement, doc)
|
||||
|
||||
var startLine = max(limit.startOffset, line)
|
||||
while (startLine - 1 > limit.startOffset && shouldSkipLine(file, doc, startLine - 1)) {
|
||||
startLine--
|
||||
}
|
||||
|
||||
var endLine = min(limit.endOffset, line)
|
||||
while (endLine + 1 < limit.endOffset && shouldSkipLine(file, doc, endLine + 1)) {
|
||||
endLine++
|
||||
}
|
||||
|
||||
val startOffset = file.getLineStartOffset(startLine) ?: return emptySet()
|
||||
val endOffset = file.getLineEndOffset(endLine) ?: return emptySet()
|
||||
|
||||
if (startOffset >= endOffset) return emptySet()
|
||||
|
||||
val lineRange = TextRange(startOffset, endOffset)
|
||||
if (lineRange.isEmpty) return emptySet()
|
||||
|
||||
val expressions = LinkedHashSet<TextWithImports>()
|
||||
|
||||
val variablesCollector = VariablesCollector(lineRange, expressions)
|
||||
containingElement.accept(variablesCollector)
|
||||
|
||||
return expressions
|
||||
}
|
||||
|
||||
private fun getContainingElement(element: PsiElement): KtElement? {
|
||||
val contElement = PsiTreeUtil.getParentOfType(element, KtDeclaration::class.java) ?: PsiTreeUtil.getParentOfType(element, KtElement::class.java)
|
||||
if (contElement is KtProperty && contElement.isLocal) {
|
||||
val parent = contElement.parent
|
||||
return getContainingElement(parent)
|
||||
}
|
||||
|
||||
if (contElement is KtDeclarationWithBody) {
|
||||
return contElement.bodyExpression
|
||||
}
|
||||
return contElement
|
||||
}
|
||||
|
||||
private fun getLineRangeForElement(containingElement: PsiElement, doc: Document): TextRange {
|
||||
val elemRange = containingElement.textRange
|
||||
return TextRange(doc.getLineNumber(elemRange.startOffset), doc.getLineNumber(elemRange.endOffset))
|
||||
}
|
||||
|
||||
private fun shouldSkipLine(file: PsiFile, doc: Document, line: Int): Boolean {
|
||||
val start = CharArrayUtil.shiftForward(doc.charsSequence, doc.getLineStartOffset(line), " \n\t")
|
||||
val end = doc.getLineEndOffset(line)
|
||||
if (start >= end) {
|
||||
return true
|
||||
}
|
||||
|
||||
val elemAtOffset = file.findElementAt(start)
|
||||
val topmostElementAtOffset = CodeInsightUtils.getTopmostElementAtOffset(elemAtOffset!!, start)
|
||||
return topmostElementAtOffset !is KtDeclaration
|
||||
}
|
||||
|
||||
private class VariablesCollector(
|
||||
private val myLineRange: TextRange,
|
||||
private val myExpressions: MutableSet<TextWithImports>
|
||||
) : KtTreeVisitorVoid() {
|
||||
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
if (element.isInRange()) {
|
||||
super.visitKtElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
|
||||
if (expression.isInRange()) {
|
||||
val selector = expression.selectorExpression
|
||||
if (selector is KtReferenceExpression) {
|
||||
if (isRefToProperty(selector)) {
|
||||
myExpressions.add(expression.createText())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visitQualifiedExpression(expression)
|
||||
}
|
||||
|
||||
private fun isRefToProperty(expression: KtReferenceExpression): Boolean {
|
||||
// NB: analyze() cannot be called here, because DELEGATED_PROPERTY_RESOLVED_CALL will be always null
|
||||
// Looks like a bug
|
||||
@Suppress("DEPRECATION")
|
||||
val context = expression.analyzeWithAllCompilerChecks().bindingContext
|
||||
val descriptor = context[BindingContext.REFERENCE_TARGET, expression]
|
||||
if (descriptor is PropertyDescriptor) {
|
||||
val getter = descriptor.getter
|
||||
return (getter == null || context[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getter] == null) &&
|
||||
descriptor.compileTimeInitializer == null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visitReferenceExpression(expression: KtReferenceExpression) {
|
||||
if (expression.isInRange()) {
|
||||
if (isRefToProperty(expression)) {
|
||||
myExpressions.add(expression.createText())
|
||||
}
|
||||
}
|
||||
super.visitReferenceExpression(expression)
|
||||
}
|
||||
|
||||
private fun KtElement.isInRange(): Boolean = myLineRange.intersects(this.textRange)
|
||||
private fun KtElement.createText(): TextWithImports = TextWithImportsImpl(CodeFragmentKind.EXPRESSION, this.text)
|
||||
|
||||
override fun visitClass(klass: KtClass) {
|
||||
// Do not show expressions used in local classes
|
||||
}
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
// Do not show expressions used in local functions
|
||||
}
|
||||
|
||||
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
|
||||
// Do not show expressions used in anonymous objects
|
||||
}
|
||||
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
// Do not show expressions used in lambdas
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger;
|
||||
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpointType;
|
||||
import com.jetbrains.javascript.debugger.JavaScriptDebugAware;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType;
|
||||
|
||||
public class KotlinJavaScriptDebugAware extends JavaScriptDebugAware {
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends XLineBreakpointType<?>> getBreakpointTypeClass() {
|
||||
return KotlinLineBreakpointType.class;
|
||||
}
|
||||
}
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.MultiRequestPositionManager
|
||||
import com.intellij.debugger.NoDataException
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.PositionManagerEx
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.requests.ClassPrepareRequestor
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.ThreeState
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.request.ClassPrepareRequest
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.core.KotlinFileTypeFactory
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineCount
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils
|
||||
|
||||
class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiRequestPositionManager, PositionManagerEx() {
|
||||
private val allKotlinFilesScope =
|
||||
object : DelegatingGlobalSearchScope(
|
||||
KotlinSourceFilterScope.projectAndLibrariesSources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project)
|
||||
) {
|
||||
private val projectIndex = ProjectRootManager.getInstance(myDebugProcess.project).fileIndex
|
||||
private val scopeComparator =
|
||||
Comparator.comparing(projectIndex::isInSourceContent)
|
||||
.thenComparing(projectIndex::isInLibrarySource)
|
||||
.thenComparing { file1, file2 -> super.compare(file1, file2) }
|
||||
|
||||
override fun compare(file1: VirtualFile, file2: VirtualFile): Int {
|
||||
return scopeComparator.compare(file1, file2)
|
||||
}
|
||||
}
|
||||
|
||||
private val sourceSearchScopes: List<GlobalSearchScope> = listOf(
|
||||
myDebugProcess.searchScope,
|
||||
allKotlinFilesScope
|
||||
)
|
||||
|
||||
override fun getAcceptedFileTypes(): Set<FileType> = KotlinFileTypeFactory.KOTLIN_FILE_TYPES_SET
|
||||
|
||||
override fun evaluateCondition(context: EvaluationContext, frame: StackFrameProxyImpl, location: Location, expression: String): ThreeState? {
|
||||
return ThreeState.UNSURE
|
||||
}
|
||||
|
||||
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? {
|
||||
if (location.isInKotlinSources()) {
|
||||
return KotlinStackFrame(frame)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getSourcePosition(location: Location?): SourcePosition? {
|
||||
if (location == null) throw NoDataException.INSTANCE
|
||||
|
||||
val fileName = location.safeSourceName() ?: throw NoDataException.INSTANCE
|
||||
val lineNumber = location.safeLineNumber()
|
||||
if (lineNumber < 0) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE
|
||||
|
||||
val psiFile = getPsiFileByLocation(location)?.let {
|
||||
replaceWithAlternativeSource(it, location)
|
||||
}
|
||||
|
||||
if (psiFile == null) {
|
||||
val isKotlinStrataAvailable = location.declaringType().containsKotlinStrata()
|
||||
if (isKotlinStrataAvailable) {
|
||||
try {
|
||||
val javaSourceFileName = location.sourceName("Java")
|
||||
val javaClassName = JvmClassName.byInternalName(defaultInternalName(location))
|
||||
val project = myDebugProcess.project
|
||||
|
||||
val defaultPsiFile = DebuggerUtils.findSourceFileForClass(
|
||||
project, sourceSearchScopes, javaClassName, javaSourceFileName, location)
|
||||
|
||||
if (defaultPsiFile != null) {
|
||||
return SourcePosition.createFromLine(defaultPsiFile, 0)
|
||||
}
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
if (psiFile !is KtFile) throw NoDataException.INSTANCE
|
||||
|
||||
val sourceLineNumber = location.safeSourceLineNumber()
|
||||
if (sourceLineNumber < 0) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
val lambdaOrFunIfInside = getLambdaOrFunIfInside(location, psiFile, sourceLineNumber)
|
||||
if (lambdaOrFunIfInside != null) {
|
||||
return SourcePosition.createFromElement(lambdaOrFunIfInside.bodyExpression!!)
|
||||
}
|
||||
|
||||
val elementInDeclaration = getElementForDeclarationLine(location, psiFile, sourceLineNumber)
|
||||
if (elementInDeclaration != null) {
|
||||
return SourcePosition.createFromElement(elementInDeclaration)
|
||||
}
|
||||
|
||||
if (sourceLineNumber > psiFile.getLineCount() && myDebugProcess.isDexDebug()) {
|
||||
val (line, ktFile) = ktLocationInfo(location, true, myDebugProcess.project, false, psiFile)
|
||||
return SourcePosition.createFromLine(ktFile ?: psiFile, line - 1)
|
||||
}
|
||||
|
||||
val sameLineLocations = location.safeMethod()?.safeAllLineLocations()?.filter {
|
||||
it.safeLineNumber() == lineNumber && it.safeSourceName() == fileName
|
||||
}
|
||||
|
||||
if (sameLineLocations != null) {
|
||||
// There're several locations for same source line. If same source position would be created for all of them,
|
||||
// breakpoints at this line will stop on every location.
|
||||
// Each location is probably some code in arguments between inlined invocations (otherwise same line locations would
|
||||
// have been merged into one), but it's impossible to correctly map locations to actual source expressions now.
|
||||
val locationIndex = sameLineLocations.indexOf(location)
|
||||
if (locationIndex > 0) {
|
||||
/*
|
||||
`finally {}` block code is placed in the class file twice.
|
||||
Unless the debugger metadata is available, we can't figure out if we are inside `finally {}`, so we have to check it using PSI.
|
||||
This is conceptually wrong and won't work in some cases, but it's still better than nothing.
|
||||
*/
|
||||
val elementAt = psiFile.getLineStartOffset(lineNumber)?.let { psiFile.findElementAt(it) }
|
||||
val isInsideDuplicatedFinally = elementAt != null && elementAt.getStrictParentOfType<KtFinallySection>() != null
|
||||
if (!isInsideDuplicatedFinally) {
|
||||
return KotlinReentrantSourcePosition(SourcePosition.createFromLine(psiFile, sourceLineNumber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SourcePosition.createFromLine(psiFile, sourceLineNumber)
|
||||
}
|
||||
|
||||
class KotlinReentrantSourcePosition(delegate: SourcePosition) : DelegateSourcePosition(delegate)
|
||||
|
||||
private fun replaceWithAlternativeSource(psiFile: PsiFile, location: Location): PsiFile {
|
||||
fun findAlternativeSource(): PsiFile? {
|
||||
val qName = location.declaringType().name()
|
||||
val alternativeFileUrl = DebuggerUtilsEx.getAlternativeSourceUrl(qName, myDebugProcess.project) ?: return null
|
||||
val alternativePsiFile = VirtualFileManager.getInstance().findFileByUrl(alternativeFileUrl) ?: return null
|
||||
return psiFile.manager.findFile(alternativePsiFile)
|
||||
}
|
||||
|
||||
return findAlternativeSource() ?: psiFile
|
||||
}
|
||||
|
||||
// Returns a property or a constructor if debugger stops at class declaration
|
||||
private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? {
|
||||
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
|
||||
val elementAt = file.findElementAt(lineStartOffset)
|
||||
val contextElement = getContextElement(elementAt)
|
||||
|
||||
if (contextElement !is KtClass) return null
|
||||
|
||||
val methodName = location.method().name()
|
||||
return when {
|
||||
JvmAbi.isGetterName(methodName) -> {
|
||||
val parameterForGetter = contextElement.primaryConstructor?.valueParameters?.firstOrNull {
|
||||
it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName
|
||||
} ?: return null
|
||||
parameterForGetter
|
||||
}
|
||||
methodName == "<init>" -> contextElement.primaryConstructor
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? {
|
||||
val currentLocationFqName = location.declaringType().name() ?: return null
|
||||
|
||||
val start = CodeInsightUtils.getStartLineOffset(file, lineNumber)
|
||||
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
|
||||
if (start == null || end == null) return null
|
||||
|
||||
val literalsOrFunctions = getLambdasAtLineIfAny(file, lineNumber)
|
||||
if (literalsOrFunctions.isEmpty()) return null
|
||||
|
||||
val elementAt = file.findElementAt(start) ?: return null
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(elementAt)
|
||||
|
||||
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName))
|
||||
.internalName.replace('/', '.')
|
||||
|
||||
for (literal in literalsOrFunctions) {
|
||||
if (InlineUtil.isInlinedArgument(literal, typeMapper.bindingContext, true)) {
|
||||
if (isInsideInlineArgument(literal, location, myDebugProcess as DebugProcessImpl, typeMapper.bindingContext)) {
|
||||
return literal
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, alwaysReturnLambdaParentClass = false)
|
||||
.getOuterClassNamesForElement(literal.firstChild)
|
||||
.classNames
|
||||
|
||||
if (internalClassNames.any { it == currentLocationClassName }) {
|
||||
return literal
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getPsiFileByLocation(location: Location): PsiFile? {
|
||||
val sourceName = location.safeSourceName() ?: return null
|
||||
|
||||
val referenceInternalName = try {
|
||||
if (location.declaringType().containsKotlinStrata()) {
|
||||
//replace is required for windows
|
||||
location.sourcePath().replace('\\', '/')
|
||||
}
|
||||
else {
|
||||
defaultInternalName(location)
|
||||
}
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
defaultInternalName(location)
|
||||
}
|
||||
|
||||
val className = JvmClassName.byInternalName(referenceInternalName)
|
||||
|
||||
val project = myDebugProcess.project
|
||||
|
||||
return DebuggerUtils.findSourceFileForClass(project, sourceSearchScopes, className, sourceName, location)
|
||||
}
|
||||
|
||||
private fun defaultInternalName(location: Location): String {
|
||||
//no stratum or source path => use default one
|
||||
val referenceFqName = location.declaringType().name()
|
||||
// JDI names are of form "package.Class$InnerClass"
|
||||
return referenceFqName.replace('.', '/')
|
||||
}
|
||||
|
||||
override fun getAllClasses(sourcePosition: SourcePosition): List<ReferenceType> {
|
||||
val psiFile = sourcePosition.file
|
||||
if (psiFile is KtFile) {
|
||||
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return emptyList()
|
||||
return DebuggerClassNameProvider(myDebugProcess).getClassesForPosition(sourcePosition)
|
||||
}
|
||||
|
||||
if (psiFile is ClsFileImpl) {
|
||||
val decompiledPsiFile = psiFile.readAction { it.decompiledPsiFile }
|
||||
if (decompiledPsiFile is KtClsFile && runReadAction { sourcePosition.line } == -1) {
|
||||
val className = JvmFileClassUtil.getFileClassInternalName(decompiledPsiFile)
|
||||
return myDebugProcess.virtualMachineProxy.classesByName(className)
|
||||
}
|
||||
}
|
||||
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
fun originalClassNamesForPosition(position: SourcePosition): List<String> {
|
||||
return DebuggerClassNameProvider(myDebugProcess, findInlineUseSites = false).getOuterClassNamesForPosition(position)
|
||||
}
|
||||
|
||||
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
|
||||
if (position.file !is KtFile) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
try {
|
||||
if (myDebugProcess.isDexDebug()) {
|
||||
val inlineLocations = runReadAction { getLocationsOfInlinedLine(type, position, myDebugProcess.searchScope) }
|
||||
if (!inlineLocations.isEmpty()) {
|
||||
return inlineLocations
|
||||
}
|
||||
}
|
||||
|
||||
val line = position.line + 1
|
||||
|
||||
val locations = type.locationsOfLine(KOTLIN_STRATA_NAME, null, line)
|
||||
if (locations == null || locations.isEmpty()) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
return locations.filter { it.sourceName(KOTLIN_STRATA_NAME) == position.file.name }
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Since Idea 14.0.3 use createPrepareRequests fun")
|
||||
override fun createPrepareRequest(classPrepareRequestor: ClassPrepareRequestor, sourcePosition: SourcePosition): ClassPrepareRequest? {
|
||||
return createPrepareRequests(classPrepareRequestor, sourcePosition).firstOrNull()
|
||||
}
|
||||
|
||||
override fun createPrepareRequests(requestor: ClassPrepareRequestor, position: SourcePosition): List<ClassPrepareRequest> {
|
||||
if (position.file !is KtFile) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
return DumbService.getInstance(myDebugProcess.project).runReadActionInSmartMode(Computable {
|
||||
val classNames = DebuggerClassNameProvider(myDebugProcess).getOuterClassNamesForPosition(position)
|
||||
classNames.flatMap { name ->
|
||||
listOfNotNull(
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, name),
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, "$name$*")
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
||||
return runReadAction { f(this) }
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger;
|
||||
|
||||
import com.intellij.debugger.PositionManager;
|
||||
import com.intellij.debugger.PositionManagerFactory;
|
||||
import com.intellij.debugger.engine.DebugProcess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class KotlinPositionManagerFactory extends PositionManagerFactory {
|
||||
@Override
|
||||
public PositionManager createPositionManager(@NotNull DebugProcess process) {
|
||||
return new KotlinPositionManager(process);
|
||||
}
|
||||
}
|
||||
+32
@@ -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.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.SourcePositionHighlighter
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral
|
||||
|
||||
class KotlinSourcePositionHighlighter: SourcePositionHighlighter() {
|
||||
override fun getHighlightRange(sourcePosition: SourcePosition?): TextRange? {
|
||||
val lambda = sourcePosition?.elementAt?.parent
|
||||
if (lambda is KtFunctionLiteral) {
|
||||
return lambda.textRange
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.SourcePositionProvider
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextUtil
|
||||
import com.intellij.debugger.impl.PositionUtil
|
||||
import com.intellij.debugger.ui.tree.FieldDescriptor
|
||||
import com.intellij.debugger.ui.tree.LocalVariableDescriptor
|
||||
import com.intellij.debugger.ui.tree.NodeDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ClassNotPreparedException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContextUtils
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
|
||||
class KotlinSourcePositionProvider : SourcePositionProvider() {
|
||||
override fun computeSourcePosition(descriptor: NodeDescriptor, project: Project, context: DebuggerContextImpl, nearest: Boolean): SourcePosition? {
|
||||
if (context.frameProxy == null) return null
|
||||
|
||||
if (descriptor is FieldDescriptor) {
|
||||
return computeSourcePosition(descriptor, project, context, nearest)
|
||||
}
|
||||
|
||||
if (descriptor is LocalVariableDescriptor) {
|
||||
return computeSourcePosition(descriptor, project, context, nearest)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun computeSourcePosition(
|
||||
descriptor: LocalVariableDescriptor,
|
||||
project: Project,
|
||||
context: DebuggerContextImpl,
|
||||
nearest: Boolean
|
||||
): SourcePosition? {
|
||||
val place = PositionUtil.getContextElement(context) ?: return null
|
||||
if (place.containingFile !is KtFile) return null
|
||||
|
||||
val contextElement = getContextElement(place) ?: return null
|
||||
|
||||
val codeFragment = KtPsiFactory(project).createExpressionCodeFragment(descriptor.name, contextElement)
|
||||
val expression = codeFragment.getContentElement()
|
||||
if (expression is KtSimpleNameExpression) {
|
||||
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
|
||||
val declarationDescriptor = BindingContextUtils.extractVariableDescriptorFromReference(bindingContext, expression)
|
||||
val sourceElement = declarationDescriptor?.source
|
||||
if (sourceElement is KotlinSourceElement) {
|
||||
val element = sourceElement.getPsi() ?: return null
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, element, element.containingFile)
|
||||
}
|
||||
return SourcePosition.createFromOffset(element.containingFile, element.textOffset)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun computeSourcePosition(descriptor: FieldDescriptor, project: Project, context: DebuggerContextImpl, nearest: Boolean): SourcePosition? {
|
||||
val fieldName = descriptor.field.name()
|
||||
|
||||
if (fieldName == AsmUtil.CAPTURED_THIS_FIELD
|
||||
|| fieldName == AsmUtil.CAPTURED_RECEIVER_FIELD
|
||||
|| fieldName.startsWith(AsmUtil.LABELED_THIS_FIELD)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = descriptor.field.declaringType()
|
||||
val myClass = findClassByType(project, type, context)?.navigationElement as? KtClassOrObject ?: return null
|
||||
|
||||
val field = myClass.declarations.firstOrNull { fieldName == it.name } ?: return null
|
||||
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, field, myClass.containingFile)
|
||||
}
|
||||
return SourcePosition.createFromOffset(field.containingFile, field.textOffset)
|
||||
}
|
||||
|
||||
private fun findClassByType(project: Project, type: ReferenceType, context: DebuggerContextImpl): PsiElement? {
|
||||
val session = context.debuggerSession
|
||||
val scope = session?.searchScope ?: GlobalSearchScope.allScope(project)
|
||||
val className = JvmClassName.byInternalName(type.name()).fqNameForClassNameWithoutDollars.asString()
|
||||
|
||||
val myClass = JavaPsiFacade.getInstance(project).findClass(className, scope)
|
||||
if (myClass != null) return myClass
|
||||
|
||||
val position = getLastSourcePosition(type, context)
|
||||
if (position != null) {
|
||||
val element = position.elementAt
|
||||
if (element != null) {
|
||||
return element.getStrictParentOfType<KtClassOrObject>()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getLastSourcePosition(type: ReferenceType, context: DebuggerContextImpl): SourcePosition? {
|
||||
val debugProcess = context.debugProcess
|
||||
if (debugProcess != null) {
|
||||
try {
|
||||
val locations = type.allLineLocations()
|
||||
if (!locations.isEmpty()) {
|
||||
val lastLocation = locations.get(locations.size - 1)
|
||||
return debugProcess.positionManager.getSourcePosition(lastLocation)
|
||||
}
|
||||
}
|
||||
catch (ignored: AbsentInformationException) {
|
||||
}
|
||||
catch (ignored: ClassNotPreparedException) {
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.descriptors.data.DescriptorData
|
||||
import com.intellij.debugger.impl.descriptors.data.DisplayKey
|
||||
import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||
import com.intellij.debugger.ui.impl.watch.ThisDescriptorImpl
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiExpression
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.xdebugger.frame.XValue
|
||||
import com.intellij.xdebugger.frame.XValueChildrenList
|
||||
import com.sun.jdi.ObjectReference
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.Type
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.THIS
|
||||
import org.jetbrains.kotlin.codegen.DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import org.jetbrains.kotlin.utils.getSafe
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
import kotlin.coroutines.Continuation
|
||||
|
||||
class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDescriptorImpl(frame, MethodsTracker()), true) {
|
||||
private companion object {
|
||||
private val LOG = Logger.getInstance(this::class.java)
|
||||
}
|
||||
|
||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||
|
||||
override fun superBuildVariables(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) {
|
||||
if (!kotlinVariableViewService.kotlinVariableView) {
|
||||
return super.superBuildVariables(evaluationContext, children)
|
||||
}
|
||||
|
||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager
|
||||
|
||||
fun addItem(variable: LocalVariableProxyImpl) {
|
||||
if (nodeManager == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
|
||||
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
||||
}
|
||||
|
||||
val (thisReferences, otherVariables) = visibleVariables
|
||||
.partition { it.name() == THIS || it is ThisLocalVariable }
|
||||
|
||||
if (!removeSyntheticThisObject(evaluationContext, children, thisReferences) && thisReferences.isNotEmpty()) {
|
||||
val thisLabels = thisReferences.asSequence()
|
||||
.filterIsInstance<ThisLocalVariable>()
|
||||
.mapNotNullTo(hashSetOf()) { it.label }
|
||||
|
||||
remapThisObjectForOuterThis(evaluationContext, children, thisLabels)
|
||||
}
|
||||
|
||||
thisReferences.forEach(::addItem)
|
||||
otherVariables.forEach(::addItem)
|
||||
}
|
||||
|
||||
private fun removeSyntheticThisObject(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
thisReferences: List<LocalVariableProxyImpl>
|
||||
): Boolean {
|
||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
|
||||
|
||||
if (thisObject.type().isSubtype(CONTINUATION_TYPE)) {
|
||||
ExistingInstanceThis.find(children)?.remove()
|
||||
return true
|
||||
}
|
||||
|
||||
val thisObjectType = thisObject.type()
|
||||
if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) {
|
||||
val existingThis = ExistingInstanceThis.find(children)
|
||||
if (existingThis != null) {
|
||||
existingThis.remove()
|
||||
val javaValue = existingThis.value as? JavaValue
|
||||
if (javaValue != null) {
|
||||
attachCapturedThisFromLambda(evaluationContext, children, javaValue, thisReferences)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return removeCallSiteThisInInlineFunction(evaluationContext, children)
|
||||
}
|
||||
|
||||
private fun removeCallSiteThisInInlineFunction(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean {
|
||||
val frameProxy = evaluationContext.frameProxy
|
||||
|
||||
val variables = frameProxy?.safeVisibleVariables() ?: return false
|
||||
val inlineDepth = getInlineDepth(variables)
|
||||
val declarationSiteThis = variables.firstOrNull { v ->
|
||||
val name = v.name()
|
||||
name.endsWith(INLINE_FUN_VAR_SUFFIX) && name.dropInlineSuffix() == AsmUtil.INLINE_DECLARATION_SITE_THIS
|
||||
}
|
||||
|
||||
if (inlineDepth > 0 && declarationSiteThis != null) {
|
||||
ExistingInstanceThis.find(children)?.remove()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun attachCapturedThisFromLambda(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
javaValue: JavaValue,
|
||||
thisReferences: List<LocalVariableProxyImpl>
|
||||
) {
|
||||
try {
|
||||
val value = javaValue.descriptor.calcValue(evaluationContext) as? ObjectReference ?: return
|
||||
val thisField = value.referenceType().fieldByName(AsmUtil.CAPTURED_THIS_FIELD) ?: return
|
||||
val thisValue = value.getValue(thisField) as? ObjectReference ?: return
|
||||
val thisType = thisValue.referenceType()
|
||||
val unsafeLabel = generateThisLabelUnsafe(thisType) ?: return
|
||||
val label = checkLabel(unsafeLabel)
|
||||
|
||||
if (label != null) {
|
||||
val thisName = getThisName(label)
|
||||
|
||||
if (thisReferences.any { it.name() == thisName }) {
|
||||
// Avoid label duplication
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val thisName = when {
|
||||
thisReferences.isEmpty() -> THIS
|
||||
label != null -> getThisName(label)
|
||||
else -> "$THIS (anonymous fun)"
|
||||
}
|
||||
|
||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager ?: return
|
||||
val thisDescriptor = nodeManager.getDescriptor(this.descriptor, LabeledThisData(thisName, thisValue))
|
||||
children.add(JavaValue.create(null, thisDescriptor, evaluationContext, nodeManager, false))
|
||||
} catch (e: EvaluateException) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private fun remapThisObjectForOuterThis(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
existingThisLabels: Set<String>
|
||||
) {
|
||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
|
||||
val variable = ExistingInstanceThis.find(children) ?: return
|
||||
|
||||
val thisLabel = generateThisLabel(thisObject.referenceType())?.takeIf { it !in existingThisLabels }
|
||||
if (thisLabel == null) {
|
||||
variable.remove()
|
||||
return
|
||||
}
|
||||
|
||||
// add additional checks?
|
||||
variable.remapName(getThisName(thisLabel))
|
||||
}
|
||||
|
||||
// Very Dirty Work-around.
|
||||
// Hopefully, there will be an API for that in 2019.1.
|
||||
private class ExistingInstanceThis(
|
||||
private val children: XValueChildrenList,
|
||||
private val index: Int,
|
||||
val value: XValue,
|
||||
private val size: Int
|
||||
) {
|
||||
companion object {
|
||||
private const val THIS_NAME = "this"
|
||||
|
||||
fun find(children: XValueChildrenList): ExistingInstanceThis? {
|
||||
val size = children.size()
|
||||
for (i in 0 until size) {
|
||||
if (children.getName(i) == THIS_NAME) {
|
||||
val valueDescriptor = (children.getValue(i) as? JavaValue)?.descriptor
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (valueDescriptor !is ThisDescriptorImpl) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ExistingInstanceThis(children, i, children.getValue(i), size)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun remapName(newName: String) {
|
||||
val (names, _) = getLists() ?: return
|
||||
names[index] = newName
|
||||
}
|
||||
|
||||
fun remove() {
|
||||
val (names, values) = getLists() ?: return
|
||||
names.removeAt(index)
|
||||
values.removeAt(index)
|
||||
}
|
||||
|
||||
private fun getLists(): Lists? {
|
||||
if (children.size() != size) {
|
||||
throw IllegalStateException("Children list was modified")
|
||||
}
|
||||
|
||||
var namesList: MutableList<Any?>? = null
|
||||
var valuesList: MutableList<Any?>? = null
|
||||
|
||||
for (field in XValueChildrenList::class.java.declaredFields) {
|
||||
val mods = field.modifiers
|
||||
if (Modifier.isPrivate(mods) && Modifier.isFinal(mods) && !Modifier.isStatic(mods) && field.type == List::class.java) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = (field.getSafe(children) as? MutableList<Any?>)?.takeIf { it.size == size } ?: continue
|
||||
|
||||
if (list[index] == THIS_NAME) {
|
||||
namesList = list
|
||||
} else if (list[index] === value) {
|
||||
valuesList = list
|
||||
}
|
||||
}
|
||||
|
||||
if (namesList != null && valuesList != null) {
|
||||
return Lists(namesList, valuesList)
|
||||
}
|
||||
}
|
||||
|
||||
LOG.error(
|
||||
"Can't find name/value lists, existing fields: "
|
||||
+ Arrays.toString(XValueChildrenList::class.java.declaredFields)
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private data class Lists(val names: MutableList<Any?>, val values: MutableList<Any?>)
|
||||
}
|
||||
|
||||
override fun getVisibleVariables(): List<LocalVariableProxyImpl> {
|
||||
val allVisibleVariables = super.getStackFrameProxy().safeVisibleVariables()
|
||||
|
||||
if (!kotlinVariableViewService.kotlinVariableView) {
|
||||
return allVisibleVariables.map { variable ->
|
||||
if (isFakeLocalVariableForInline(variable.name())) variable.wrapSyntheticInlineVariable() else variable
|
||||
}
|
||||
}
|
||||
|
||||
val inlineDepth = getInlineDepth(allVisibleVariables)
|
||||
|
||||
val (thisVariables, otherVariables) = allVisibleVariables.asSequence()
|
||||
.filter { !isHidden(it, inlineDepth) }
|
||||
.partition {
|
||||
it.name() == THIS
|
||||
|| it.name() == AsmUtil.THIS_IN_DEFAULT_IMPLS
|
||||
|| it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
|| (INLINED_THIS_REGEX.matches(it.name()))
|
||||
}
|
||||
|
||||
val (mainThis, otherThis) = thisVariables
|
||||
.sortedByDescending { it.variable }
|
||||
.let { it.firstOrNull() to it.drop(1) }
|
||||
|
||||
val remappedMainThis = mainThis?.clone(THIS, null)
|
||||
val remappedOther = (otherThis + otherVariables).map { it.remapVariableNameIfNeeded() }
|
||||
return (listOfNotNull(remappedMainThis) + remappedOther).sortedBy { it.variable }
|
||||
}
|
||||
|
||||
private fun isHidden(variable: LocalVariableProxyImpl, inlineDepth: Int): Boolean {
|
||||
val name = variable.name()
|
||||
return isFakeLocalVariableForInline(name)
|
||||
|| name.startsWith(DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX)
|
||||
|| name.startsWith(AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX)
|
||||
|| getInlineDepth(variable.name()) != inlineDepth
|
||||
|| name == CONTINUATION_VARIABLE_NAME
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.remapVariableNameIfNeeded(): LocalVariableProxyImpl {
|
||||
val name = this.name().dropInlineSuffix()
|
||||
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
return when {
|
||||
isLabeledThisReference() -> {
|
||||
val label = name.drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
||||
clone(getThisName(label), label)
|
||||
}
|
||||
name == AsmUtil.THIS_IN_DEFAULT_IMPLS -> clone(THIS + " (outer)", null)
|
||||
name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(THIS + " (receiver)", null)
|
||||
INLINED_THIS_REGEX.matches(name) -> {
|
||||
val label = generateThisLabel(frame.getValue(this)?.type())
|
||||
if (label != null) {
|
||||
clone(getThisName(label), label)
|
||||
} else {
|
||||
this@remapVariableNameIfNeeded
|
||||
}
|
||||
}
|
||||
name != this.name() -> {
|
||||
object : LocalVariableProxyImpl(frame, variable) {
|
||||
override fun name() = name
|
||||
}
|
||||
}
|
||||
else -> this@remapVariableNameIfNeeded
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateThisLabel(type: Type?): String? {
|
||||
return checkLabel(generateThisLabelUnsafe(type) ?: return null)
|
||||
}
|
||||
|
||||
private fun generateThisLabelUnsafe(type: Type?): String? {
|
||||
val referenceType = type as? ReferenceType ?: return null
|
||||
return referenceType.name().substringAfterLast('.').substringAfterLast('$')
|
||||
}
|
||||
|
||||
private fun checkLabel(label: String): String? {
|
||||
if (label.isEmpty() || label.all { it.isDigit() }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
private fun String.dropInlineSuffix(): String {
|
||||
val depth = getInlineDepth(this)
|
||||
if (depth == 0) {
|
||||
return this
|
||||
}
|
||||
|
||||
return dropLast(depth * INLINE_FUN_VAR_SUFFIX.length)
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.clone(name: String, label: String?): LocalVariableProxyImpl {
|
||||
return object : LocalVariableProxyImpl(frame, variable), ThisLocalVariable {
|
||||
override fun name() = name
|
||||
override val label = label
|
||||
}
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.isLabeledThisReference(): Boolean {
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
return name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
}
|
||||
}
|
||||
|
||||
private interface ThisLocalVariable {
|
||||
val label: String?
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableProxyImpl {
|
||||
val proxyWrapper = object : StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom) {
|
||||
override fun getValue(localVariable: LocalVariableProxyImpl): Value {
|
||||
return frame.virtualMachine.mirrorOfVoid()
|
||||
}
|
||||
}
|
||||
return LocalVariableProxyImpl(proxyWrapper, variable)
|
||||
}
|
||||
|
||||
private fun getThisName(label: String): String {
|
||||
return "$THIS (@$label)"
|
||||
}
|
||||
|
||||
private class LabeledThisData(val name: String, val value: ObjectReference) : DescriptorData<ValueDescriptorImpl>() {
|
||||
override fun createDescriptorImpl(project: Project): ValueDescriptorImpl {
|
||||
return object : ValueDescriptorImpl(project, value) {
|
||||
override fun getName() = this@LabeledThisData.name
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = value
|
||||
override fun canSetValue() = false
|
||||
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression {
|
||||
// TODO change to labeled this
|
||||
val elementFactory = JavaPsiFacade.getElementFactory(myProject)
|
||||
try {
|
||||
return elementFactory.createExpressionFromText("this", null)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
throw EvaluateException(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDisplayKey(): DisplayKey<ValueDescriptorImpl> = SimpleDisplayKey(this)
|
||||
override fun equals(other: Any?) = other is LabeledThisData && other.name == name
|
||||
override fun hashCode() = name.hashCode()
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.ToggleAction
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.impl.XDebuggerUtilImpl
|
||||
import org.jetbrains.kotlin.idea.core.KotlinFileTypeFactory
|
||||
|
||||
class ToggleKotlinVariablesState {
|
||||
companion object {
|
||||
private const val KOTLIN_VARIABLE_VIEW = "debugger.kotlin.variable.view"
|
||||
|
||||
fun getService(): ToggleKotlinVariablesState {
|
||||
return ServiceManager.getService(ToggleKotlinVariablesState::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
var kotlinVariableView = PropertiesComponent.getInstance().getBoolean(KOTLIN_VARIABLE_VIEW, true)
|
||||
set(newValue) {
|
||||
field = newValue
|
||||
PropertiesComponent.getInstance().setValue(KOTLIN_VARIABLE_VIEW, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleKotlinVariablesView : ToggleAction() {
|
||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
super.update(e)
|
||||
val session = XDebugSession.DATA_KEY.getData(e.dataContext)
|
||||
e.presentation.isEnabledAndVisible = session != null && session.isInKotlinFile()
|
||||
}
|
||||
|
||||
private fun XDebugSession.isInKotlinFile(): Boolean {
|
||||
val fileExtension = currentPosition?.file?.extension ?: return false
|
||||
return fileExtension in KotlinFileTypeFactory.KOTLIN_EXTENSIONS
|
||||
}
|
||||
|
||||
override fun isSelected(e: AnActionEvent) = kotlinVariableViewService.kotlinVariableView
|
||||
|
||||
override fun setSelected(e: AnActionEvent, state: Boolean) {
|
||||
kotlinVariableViewService.kotlinVariableView = state
|
||||
XDebuggerUtilImpl.rebuildAllSessionsViews(e.project)
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinBreakpointFiltersPanel">
|
||||
<grid id="27dc6" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="5736" binding="myConditionsPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="7">
|
||||
<margin top="2" left="2" bottom="5" right="5"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<clientProperties>
|
||||
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithoutIndent"/>
|
||||
</clientProperties>
|
||||
<border type="etched" title-resource-bundle="messages/DebuggerBundle" title-key="label.breakpoint.properties.panel.group.conditions"/>
|
||||
<children>
|
||||
<grid id="8e867" binding="myInstanceFiltersPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="17e11" class="javax.swing.JCheckBox" binding="myInstanceFiltersCheckBox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.instance.filters"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="5231f" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<hspacer id="eeee7">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<xy id="28068" binding="myInstanceFiltersFieldPanel" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</xy>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="25884" binding="myClassFiltersPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="729d5" class="javax.swing.JCheckBox" binding="myClassFiltersCheckBox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.class.filters"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="9bef6" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<xy id="e3d10" binding="myClassFiltersFieldPanel" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</xy>
|
||||
<hspacer id="ec2a">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="275ca" binding="myPassCountPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="27bbc" class="javax.swing.JCheckBox" binding="myPassCountCheckbox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.pass.count"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="71095" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="33fef" class="javax.swing.JTextField" binding="myPassCountField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties>
|
||||
<enabled value="false"/>
|
||||
<horizontalAlignment value="10"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="28e6f">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<vspacer id="d1c29">
|
||||
<constraints>
|
||||
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.breakpoints;
|
||||
|
||||
import com.intellij.debugger.InstanceFilter;
|
||||
import com.intellij.debugger.ui.breakpoints.EditClassFiltersDialog;
|
||||
import com.intellij.debugger.ui.breakpoints.EditInstanceFiltersDialog;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.ui.FieldPanel;
|
||||
import com.intellij.ui.MultiLineTooltipUI;
|
||||
import com.intellij.ui.classFilter.ClassFilter;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
|
||||
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class KotlinBreakpointFiltersPanel<T extends KotlinPropertyBreakpointProperties, B extends XBreakpoint<T>> extends XBreakpointCustomPropertiesPanel<B> {
|
||||
private JPanel myConditionsPanel;
|
||||
private JPanel myInstanceFiltersPanel;
|
||||
private JCheckBox myInstanceFiltersCheckBox;
|
||||
private JPanel myInstanceFiltersFieldPanel;
|
||||
private JPanel myClassFiltersPanel;
|
||||
private JCheckBox myClassFiltersCheckBox;
|
||||
private JPanel myClassFiltersFieldPanel;
|
||||
private JPanel myPassCountPanel;
|
||||
private JCheckBox myPassCountCheckbox;
|
||||
private JTextField myPassCountField;
|
||||
|
||||
private final FieldPanel myInstanceFiltersField;
|
||||
private final FieldPanel myClassFiltersField;
|
||||
|
||||
private ClassFilter[] myClassFilters = ClassFilter.EMPTY_ARRAY;
|
||||
private ClassFilter[] myClassExclusionFilters = ClassFilter.EMPTY_ARRAY;
|
||||
private InstanceFilter[] myInstanceFilters = InstanceFilter.EMPTY_ARRAY;
|
||||
protected final Project myProject;
|
||||
|
||||
private PsiClass myBreakpointPsiClass;
|
||||
|
||||
public KotlinBreakpointFiltersPanel(Project project) {
|
||||
myProject = project;
|
||||
myInstanceFiltersField = new FieldPanel(new MyTextField(), "", null,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
reloadInstanceFilters();
|
||||
EditInstanceFiltersDialog _dialog = new EditInstanceFiltersDialog(myProject);
|
||||
_dialog.setFilters(myInstanceFilters);
|
||||
_dialog.show();
|
||||
if (_dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
myInstanceFilters = _dialog.getFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
myClassFiltersField = new FieldPanel(new MyTextField(), "", null,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
reloadClassFilters();
|
||||
|
||||
com.intellij.ide.util.ClassFilter classFilter = createClassConditionFilter();
|
||||
|
||||
EditClassFiltersDialog _dialog = new EditClassFiltersDialog(myProject, classFilter);
|
||||
_dialog.setFilters(myClassFilters, myClassExclusionFilters);
|
||||
_dialog.show();
|
||||
if (_dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
myClassFilters = _dialog.getFilters();
|
||||
myClassExclusionFilters = _dialog.getExclusionFilters();
|
||||
updateClassFilterEditor(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
ActionListener updateListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
updateCheckboxes();
|
||||
}
|
||||
};
|
||||
|
||||
myPassCountCheckbox.addActionListener(updateListener);
|
||||
myInstanceFiltersCheckBox.addActionListener(updateListener);
|
||||
myClassFiltersCheckBox.addActionListener(updateListener);
|
||||
|
||||
ToolTipManager.sharedInstance().registerComponent(myClassFiltersField.getTextField());
|
||||
ToolTipManager.sharedInstance().registerComponent(myInstanceFiltersField.getTextField());
|
||||
|
||||
insert(myInstanceFiltersFieldPanel, myInstanceFiltersField);
|
||||
insert(myClassFiltersFieldPanel, myClassFiltersField);
|
||||
|
||||
DebuggerUIUtil.focusEditorOnCheck(myPassCountCheckbox, myPassCountField);
|
||||
DebuggerUIUtil.focusEditorOnCheck(myInstanceFiltersCheckBox, myInstanceFiltersField.getTextField());
|
||||
DebuggerUIUtil.focusEditorOnCheck(myClassFiltersCheckBox, myClassFiltersField.getTextField());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myConditionsPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisibleOnPopup(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
return properties.isCOUNT_FILTER_ENABLED() || properties.isCLASS_FILTERS_ENABLED() || properties.isINSTANCE_FILTERS_ENABLED();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveTo(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
try {
|
||||
String text = myPassCountField.getText().trim();
|
||||
int filter = !text.isEmpty() ? Integer.parseInt(text) : 0;
|
||||
if (filter < 0) filter = 0;
|
||||
changed = properties.setCOUNT_FILTER(filter);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
|
||||
changed = properties.setCOUNT_FILTER_ENABLED(properties.getCOUNT_FILTER() > 0 && myPassCountCheckbox.isSelected()) || changed;
|
||||
reloadInstanceFilters();
|
||||
reloadClassFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
updateClassFilterEditor(true);
|
||||
|
||||
changed = properties.setINSTANCE_FILTERS_ENABLED(myInstanceFiltersField.getText().length() > 0 && myInstanceFiltersCheckBox.isSelected()) || changed;
|
||||
changed = properties.setCLASS_FILTERS_ENABLED(myClassFiltersField.getText().length() > 0 && myClassFiltersCheckBox.isSelected()) || changed;
|
||||
changed = properties.setClassFilters(myClassFilters) || changed;
|
||||
changed = properties.setClassExclusionFilters(myClassExclusionFilters) || changed;
|
||||
changed = properties.setInstanceFilters(myInstanceFilters) || changed;
|
||||
if (changed) {
|
||||
((XBreakpointBase)breakpoint).fireBreakpointChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private static void insert(JPanel panel, JComponent component) {
|
||||
panel.setLayout(new BorderLayout());
|
||||
panel.add(component, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFrom(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
if (properties.getCOUNT_FILTER() > 0) {
|
||||
myPassCountField.setText(Integer.toString(properties.getCOUNT_FILTER()));
|
||||
}
|
||||
else {
|
||||
myPassCountField.setText("");
|
||||
}
|
||||
|
||||
myPassCountCheckbox.setSelected(properties.isCOUNT_FILTER_ENABLED());
|
||||
|
||||
myInstanceFiltersCheckBox.setSelected(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFiltersField.setEnabled(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFiltersField.getTextField().setEditable(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFilters = properties.getInstanceFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
|
||||
myClassFiltersCheckBox.setSelected(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFiltersField.setEnabled(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFiltersField.getTextField().setEditable(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFilters = properties.getClassFilters();
|
||||
myClassExclusionFilters = properties.getClassExclusionFilters();
|
||||
updateClassFilterEditor(true);
|
||||
|
||||
XSourcePosition position = breakpoint.getSourcePosition();
|
||||
// TODO: need to calculate psi class
|
||||
//myBreakpointPsiClass = breakpoint.getPsiClass();
|
||||
}
|
||||
updateCheckboxes();
|
||||
}
|
||||
|
||||
private void updateInstanceFilterEditor(boolean updateText) {
|
||||
List<String> filters = new ArrayList<String>();
|
||||
for (InstanceFilter instanceFilter : myInstanceFilters) {
|
||||
if (instanceFilter.isEnabled()) {
|
||||
filters.add(Long.toString(instanceFilter.getId()));
|
||||
}
|
||||
}
|
||||
if (updateText) {
|
||||
myInstanceFiltersField.setText(StringUtil.join(filters, " "));
|
||||
}
|
||||
|
||||
String tipText = concatWithEx(filters, " ", (int)Math.sqrt(myInstanceFilters.length) + 1, "\n");
|
||||
myInstanceFiltersField.getTextField().setToolTipText(tipText);
|
||||
}
|
||||
|
||||
private class MyTextField extends JTextField {
|
||||
public MyTextField() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent event) {
|
||||
reloadClassFilters();
|
||||
updateClassFilterEditor(false);
|
||||
reloadInstanceFilters();
|
||||
updateInstanceFilterEditor(false);
|
||||
String toolTipText = super.getToolTipText(event);
|
||||
return getToolTipText().length() == 0 ? null : toolTipText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JToolTip createToolTip() {
|
||||
JToolTip toolTip = new JToolTip(){{
|
||||
setUI(new MultiLineTooltipUI());
|
||||
}};
|
||||
toolTip.setComponent(this);
|
||||
return toolTip;
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadClassFilters() {
|
||||
String filtersText = myClassFiltersField.getText();
|
||||
|
||||
ArrayList<ClassFilter> classFilters = new ArrayList<ClassFilter>();
|
||||
ArrayList<ClassFilter> exclusionFilters = new ArrayList<ClassFilter>();
|
||||
int startFilter = -1;
|
||||
for(int i = 0; i <= filtersText.length(); i++) {
|
||||
if(i < filtersText.length() && !Character.isWhitespace(filtersText.charAt(i))){
|
||||
if(startFilter == -1) {
|
||||
startFilter = i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(startFilter >=0) {
|
||||
if(filtersText.charAt(startFilter) == '-') {
|
||||
exclusionFilters.add(new ClassFilter(filtersText.substring(startFilter + 1, i)));
|
||||
}
|
||||
else {
|
||||
classFilters.add(new ClassFilter(filtersText.substring(startFilter, i)));
|
||||
}
|
||||
startFilter = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ClassFilter classFilter : myClassFilters) {
|
||||
if (!classFilter.isEnabled()) {
|
||||
classFilters.add(classFilter);
|
||||
}
|
||||
}
|
||||
for (ClassFilter classFilter : myClassExclusionFilters) {
|
||||
if (!classFilter.isEnabled()) {
|
||||
exclusionFilters.add(classFilter);
|
||||
}
|
||||
}
|
||||
myClassFilters = classFilters .toArray(new ClassFilter[classFilters .size()]);
|
||||
myClassExclusionFilters = exclusionFilters.toArray(new ClassFilter[exclusionFilters.size()]);
|
||||
}
|
||||
|
||||
private void reloadInstanceFilters() {
|
||||
String filtersText = myInstanceFiltersField.getText();
|
||||
|
||||
ArrayList<InstanceFilter> idxs = new ArrayList<InstanceFilter>();
|
||||
int startNumber = -1;
|
||||
for(int i = 0; i <= filtersText.length(); i++) {
|
||||
if(i < filtersText.length() && Character.isDigit(filtersText.charAt(i))) {
|
||||
if(startNumber == -1) {
|
||||
startNumber = i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(startNumber >=0) {
|
||||
idxs.add(InstanceFilter.create(filtersText.substring(startNumber, i)));
|
||||
startNumber = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (InstanceFilter instanceFilter : myInstanceFilters) {
|
||||
if (!instanceFilter.isEnabled()) {
|
||||
idxs.add(instanceFilter);
|
||||
}
|
||||
}
|
||||
myInstanceFilters = idxs.toArray(new InstanceFilter[idxs.size()]);
|
||||
}
|
||||
|
||||
private void updateClassFilterEditor(boolean updateText) {
|
||||
List<String> filters = new ArrayList<String>();
|
||||
for (ClassFilter classFilter : myClassFilters) {
|
||||
if (classFilter.isEnabled()) {
|
||||
filters.add(classFilter.getPattern());
|
||||
}
|
||||
}
|
||||
List<String> excludeFilters = new ArrayList<String>();
|
||||
for (ClassFilter classFilter : myClassExclusionFilters) {
|
||||
if (classFilter.isEnabled()) {
|
||||
excludeFilters.add("-" + classFilter.getPattern());
|
||||
}
|
||||
}
|
||||
if (updateText) {
|
||||
String editorText = StringUtil.join(filters, " ");
|
||||
if(!filters.isEmpty()) {
|
||||
editorText += " ";
|
||||
}
|
||||
editorText += StringUtil.join(excludeFilters, " ");
|
||||
myClassFiltersField.setText(editorText);
|
||||
}
|
||||
|
||||
int width = (int)Math.sqrt(myClassExclusionFilters.length + myClassFilters.length) + 1;
|
||||
String tipText = concatWithEx(filters, " ", width, "\n");
|
||||
if(!filters.isEmpty()) {
|
||||
tipText += "\n";
|
||||
}
|
||||
tipText += concatWithEx(excludeFilters, " ", width, "\n");
|
||||
myClassFiltersField.getTextField().setToolTipText(tipText);
|
||||
}
|
||||
|
||||
private static String concatWithEx(List<String> s, String concator, int N, String NthConcator) {
|
||||
String result = "";
|
||||
int i = 1;
|
||||
for (Iterator iterator = s.iterator(); iterator.hasNext(); i++) {
|
||||
String str = (String) iterator.next();
|
||||
result += str;
|
||||
if(iterator.hasNext()){
|
||||
if(i % N == 0){
|
||||
result += NthConcator;
|
||||
}
|
||||
else {
|
||||
result += concator;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected com.intellij.ide.util.ClassFilter createClassConditionFilter() {
|
||||
com.intellij.ide.util.ClassFilter classFilter;
|
||||
if(myBreakpointPsiClass != null) {
|
||||
classFilter = new com.intellij.ide.util.ClassFilter() {
|
||||
@Override
|
||||
public boolean isAccepted(PsiClass aClass) {
|
||||
return myBreakpointPsiClass == aClass || aClass.isInheritor(myBreakpointPsiClass, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
classFilter = null;
|
||||
}
|
||||
return classFilter;
|
||||
}
|
||||
|
||||
protected void updateCheckboxes() {
|
||||
boolean passCountApplicable = true;
|
||||
if (myInstanceFiltersCheckBox.isSelected() || myClassFiltersCheckBox.isSelected()) {
|
||||
passCountApplicable = false;
|
||||
}
|
||||
myPassCountCheckbox.setEnabled(passCountApplicable);
|
||||
|
||||
boolean passCountSelected = myPassCountCheckbox.isSelected();
|
||||
myInstanceFiltersCheckBox.setEnabled(!passCountSelected);
|
||||
myClassFiltersCheckBox.setEnabled(!passCountSelected);
|
||||
|
||||
myPassCountField.setEditable(myPassCountCheckbox.isSelected());
|
||||
myPassCountField.setEnabled (myPassCountCheckbox.isSelected());
|
||||
|
||||
myInstanceFiltersField.setEnabled(myInstanceFiltersCheckBox.isSelected());
|
||||
myInstanceFiltersField.getTextField().setEditable(myInstanceFiltersCheckBox.isSelected());
|
||||
|
||||
myClassFiltersField.setEnabled(myClassFiltersCheckBox.isSelected());
|
||||
myClassFiltersField.getTextField().setEditable(myClassFiltersCheckBox.isSelected());
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.JavaBreakpointHandler
|
||||
import com.intellij.debugger.engine.JavaBreakpointHandlerFactory
|
||||
|
||||
class KotlinFieldBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
|
||||
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
||||
return KotlinFieldBreakpointHandler(process)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinLineBreakpointHandlerFactory: JavaBreakpointHandlerFactory {
|
||||
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
||||
return KotlinLineBreakpointHandler(process)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinFieldBreakpointType::class.java, process)
|
||||
class KotlinLineBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinLineBreakpointType::class.java, process)
|
||||
+397
@@ -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.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.PositionUtil
|
||||
import com.intellij.debugger.requests.Requestor
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointCategory
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointWithHighlighter
|
||||
import com.intellij.debugger.ui.breakpoints.FieldBreakpoint
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Method
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.event.*
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import com.sun.jdi.request.MethodEntryRequest
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.debugger.safeAllLineLocations
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinFieldBreakpoint(
|
||||
project: Project,
|
||||
breakpoint: XBreakpoint<KotlinPropertyBreakpointProperties>
|
||||
): BreakpointWithHighlighter<KotlinPropertyBreakpointProperties>(project, breakpoint) {
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint")
|
||||
private val CATEGORY: Key<FieldBreakpoint> = BreakpointCategory.lookup<FieldBreakpoint>("field_breakpoints")
|
||||
}
|
||||
|
||||
private enum class BreakpointType {
|
||||
FIELD,
|
||||
METHOD
|
||||
}
|
||||
|
||||
private var breakpointType: BreakpointType = BreakpointType.FIELD
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (!BreakpointWithHighlighter.isPositionValid(xBreakpoint.sourcePosition)) return false
|
||||
|
||||
return runReadAction {
|
||||
val field = getField()
|
||||
field != null && field.isValid
|
||||
}
|
||||
}
|
||||
|
||||
fun getField(): KtCallableDeclaration? {
|
||||
val sourcePosition = sourcePosition
|
||||
return getProperty(sourcePosition)
|
||||
}
|
||||
|
||||
private fun getProperty(sourcePosition: SourcePosition?): KtCallableDeclaration? {
|
||||
val property: KtProperty? = PositionUtil.getPsiElementAt(project, KtProperty::class.java, sourcePosition)
|
||||
if (property != null) {
|
||||
return property
|
||||
}
|
||||
val parameter: KtParameter? = PositionUtil.getPsiElementAt(project, KtParameter::class.java, sourcePosition)
|
||||
if (parameter != null) {
|
||||
return parameter
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun reload() {
|
||||
super.reload()
|
||||
|
||||
val property = getProperty(sourcePosition) ?: return
|
||||
val propertyName = property.name ?: return
|
||||
setFieldName(propertyName)
|
||||
|
||||
if (property is KtProperty && property.isTopLevel) {
|
||||
properties.myClassName = JvmFileClassUtil.getFileClassInfoNoResolve(property.getContainingKtFile()).fileClassFqName.asString()
|
||||
} else {
|
||||
val ktClass: KtClassOrObject? = PsiTreeUtil.getParentOfType(property, KtClassOrObject::class.java)
|
||||
if (ktClass is KtClassOrObject) {
|
||||
val fqName = ktClass.fqName
|
||||
if (fqName != null) {
|
||||
properties.myClassName = fqName.asString()
|
||||
}
|
||||
}
|
||||
}
|
||||
isInstanceFiltersEnabled = false
|
||||
}
|
||||
|
||||
override fun createRequestForPreparedClass(debugProcess: DebugProcessImpl?, refType: ReferenceType?) {
|
||||
if (debugProcess == null || refType == null) return
|
||||
|
||||
val property = getProperty(sourcePosition) ?: return
|
||||
|
||||
breakpointType = (computeBreakpointType(property) ?: return)
|
||||
|
||||
val vm = debugProcess.virtualMachineProxy
|
||||
try {
|
||||
if (properties.WATCH_INITIALIZATION) {
|
||||
val sourcePosition = sourcePosition
|
||||
if (sourcePosition != null) {
|
||||
debugProcess.positionManager
|
||||
.locationsOfLine(refType, sourcePosition)
|
||||
.filter { it.method().isConstructor || it.method().isStaticInitializer }
|
||||
.forEach {
|
||||
val request = debugProcess.requestsManager.createBreakpointRequest(this, it)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Breakpoint request added")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (breakpointType) {
|
||||
BreakpointType.FIELD -> {
|
||||
val field = refType.fieldByName(getFieldName())
|
||||
if (field != null) {
|
||||
val manager = debugProcess.requestsManager
|
||||
if (properties.WATCH_MODIFICATION && vm.canWatchFieldModification()) {
|
||||
val request = manager.createModificationWatchpointRequest(this, field)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Modification request added")
|
||||
}
|
||||
}
|
||||
if (properties.WATCH_ACCESS && vm.canWatchFieldAccess()) {
|
||||
val request = manager.createAccessWatchpointRequest(this, field)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Field access request added (field = ${field.name()}; refType = ${refType.name()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BreakpointType.METHOD -> {
|
||||
val fieldName = getFieldName()
|
||||
|
||||
if (properties.WATCH_ACCESS) {
|
||||
val getter = refType.methodsByName(JvmAbi.getterName(fieldName)).firstOrNull()
|
||||
if (getter != null) {
|
||||
createMethodBreakpoint(debugProcess, refType, getter)
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.WATCH_MODIFICATION) {
|
||||
val setter = refType.methodsByName(JvmAbi.setterName(fieldName)).firstOrNull()
|
||||
if (setter != null) {
|
||||
createMethodBreakpoint(debugProcess, refType, setter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
LOG.debug(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeBreakpointType(property: KtCallableDeclaration): BreakpointType? {
|
||||
return runReadAction {
|
||||
val bindingContext = property.analyze()
|
||||
var descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, property)
|
||||
if (descriptor is ValueParameterDescriptor) {
|
||||
descriptor = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor)
|
||||
}
|
||||
|
||||
if (descriptor is PropertyDescriptor) {
|
||||
if (bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor)!!) {
|
||||
BreakpointType.FIELD
|
||||
}
|
||||
else {
|
||||
BreakpointType.METHOD
|
||||
}
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMethodBreakpoint(debugProcess: DebugProcessImpl, refType: ReferenceType, accessor: Method) {
|
||||
val manager = debugProcess.requestsManager
|
||||
val line = accessor.safeAllLineLocations().firstOrNull()
|
||||
if (line != null) {
|
||||
val request = manager.createBreakpointRequest(this, line)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Breakpoint request added")
|
||||
}
|
||||
}
|
||||
else {
|
||||
var entryRequest: MethodEntryRequest? = findRequest(debugProcess, MethodEntryRequest::class.java, this)
|
||||
if (entryRequest == null) {
|
||||
entryRequest = manager.createMethodEntryRequest(this)!!
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Method entry request added (method = ${accessor.name()}; refType = ${refType.name()})")
|
||||
}
|
||||
}
|
||||
else {
|
||||
entryRequest.disable()
|
||||
}
|
||||
entryRequest.addClassFilter(refType)
|
||||
manager.enableRequest(entryRequest)
|
||||
}
|
||||
}
|
||||
|
||||
inline private fun <reified T : EventRequest> findRequest(debugProcess: DebugProcessImpl, requestClass: Class<T>, requestor: Requestor): T? {
|
||||
val requests = debugProcess.requestsManager.findRequests(requestor)
|
||||
for (eventRequest in requests) {
|
||||
if (eventRequest::class.java == requestClass) {
|
||||
return eventRequest as T
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun evaluateCondition(context: EvaluationContextImpl, event: LocatableEvent): Boolean {
|
||||
if (breakpointType == BreakpointType.METHOD && !matchesEvent(event)) {
|
||||
return false
|
||||
}
|
||||
return super.evaluateCondition(context, event)
|
||||
}
|
||||
|
||||
fun matchesEvent(event: LocatableEvent): Boolean {
|
||||
val method = event.location()?.method()
|
||||
// TODO check property type
|
||||
return method != null && method.name() in getMethodsName()
|
||||
}
|
||||
|
||||
private fun getMethodsName(): List<String> {
|
||||
val fieldName = getFieldName()
|
||||
return listOf(JvmAbi.getterName(fieldName), JvmAbi.setterName(fieldName))
|
||||
}
|
||||
|
||||
override fun getEventMessage(event: LocatableEvent): String {
|
||||
val location = event.location()!!
|
||||
val locationQName = location.declaringType().name() + "." + location.method().name()
|
||||
val locationFileName = try {
|
||||
location.sourceName()
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
fileName
|
||||
}
|
||||
catch (e: InternalError) {
|
||||
fileName
|
||||
}
|
||||
|
||||
val locationLine = location.lineNumber()
|
||||
when (event) {
|
||||
is ModificationWatchpointEvent-> {
|
||||
val field = event.field()
|
||||
return DebuggerBundle.message(
|
||||
"status.static.field.watchpoint.reached.access",
|
||||
field.declaringType().name(),
|
||||
field.name(),
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is AccessWatchpointEvent -> {
|
||||
val field = event.field()
|
||||
return DebuggerBundle.message(
|
||||
"status.static.field.watchpoint.reached.access",
|
||||
field.declaringType().name(),
|
||||
field.name(),
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is MethodEntryEvent -> {
|
||||
val method = event.method()
|
||||
return DebuggerBundle.message(
|
||||
"status.method.entry.breakpoint.reached",
|
||||
method.declaringType().name() + "." + method.name() + "()",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is MethodExitEvent -> {
|
||||
val method = event.method()
|
||||
return DebuggerBundle.message(
|
||||
"status.method.exit.breakpoint.reached",
|
||||
method.declaringType().name() + "." + method.name() + "()",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
}
|
||||
return DebuggerBundle.message(
|
||||
"status.line.breakpoint.reached",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
|
||||
fun setFieldName(fieldName: String) {
|
||||
properties.myFieldName = fieldName
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchAccess(value: Boolean) {
|
||||
properties.WATCH_ACCESS = value
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchModification(value: Boolean) {
|
||||
properties.WATCH_MODIFICATION = value
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchInitialization(value: Boolean) {
|
||||
properties.WATCH_INITIALIZATION = value
|
||||
}
|
||||
|
||||
override fun getDisabledIcon(isMuted: Boolean): Icon {
|
||||
val master = DebuggerManagerEx.getInstanceEx(myProject).breakpointManager.findMasterBreakpoint(this)
|
||||
return when {
|
||||
isMuted && master == null -> AllIcons.Debugger.Db_muted_disabled_field_breakpoint
|
||||
isMuted && master != null -> AllIcons.Debugger.Db_muted_dep_field_breakpoint
|
||||
master != null -> AllIcons.Debugger.Db_dep_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_disabled_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSetIcon(isMuted: Boolean): Icon {
|
||||
return when {
|
||||
isMuted -> AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
// BUNCH: 182
|
||||
override fun getInvalidIcon(isMuted: Boolean): Icon {
|
||||
return AllIcons.Debugger.Db_invalid_breakpoint
|
||||
}
|
||||
|
||||
override fun getVerifiedIcon(isMuted: Boolean): Icon {
|
||||
return when {
|
||||
isMuted -> AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_verified_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVerifiedWarningsIcon(isMuted: Boolean): Icon = AllIcons.Debugger.Db_exception_breakpoint
|
||||
|
||||
override fun getCategory() = CATEGORY
|
||||
|
||||
override fun getDisplayName(): String? {
|
||||
if (!isValid) {
|
||||
return DebuggerBundle.message("status.breakpoint.invalid")
|
||||
}
|
||||
val className = className
|
||||
return if (className != null && !className.isEmpty()) className + "." + getFieldName() else getFieldName()
|
||||
}
|
||||
|
||||
private fun getFieldName(): String {
|
||||
val declaration = getField()
|
||||
return runReadAction { declaration?.name } ?: "unknown"
|
||||
}
|
||||
|
||||
override fun getEvaluationElement(): PsiElement? {
|
||||
return getField()
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.util.ui.DialogUtil
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel
|
||||
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.Box
|
||||
import javax.swing.JCheckBox
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class KotlinFieldBreakpointPropertiesPanel: XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>() {
|
||||
private var myWatchInitializationCheckBox: JCheckBox by Delegates.notNull()
|
||||
private var myWatchAccessCheckBox: JCheckBox by Delegates.notNull()
|
||||
private var myWatchModificationCheckBox: JCheckBox by Delegates.notNull()
|
||||
|
||||
override fun getComponent(): JComponent {
|
||||
myWatchInitializationCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.initialization.label"))
|
||||
myWatchAccessCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.access.label"))
|
||||
myWatchModificationCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.modification.label"))
|
||||
|
||||
DialogUtil.registerMnemonic(myWatchInitializationCheckBox)
|
||||
DialogUtil.registerMnemonic(myWatchAccessCheckBox)
|
||||
DialogUtil.registerMnemonic(myWatchModificationCheckBox)
|
||||
|
||||
fun Box.addNewPanelForCheckBox(checkBox: JCheckBox) {
|
||||
val panel = JPanel(BorderLayout())
|
||||
panel.add(checkBox, BorderLayout.NORTH)
|
||||
this.add(panel)
|
||||
}
|
||||
|
||||
val watchBox = Box.createVerticalBox()
|
||||
watchBox.addNewPanelForCheckBox(myWatchInitializationCheckBox)
|
||||
watchBox.addNewPanelForCheckBox(myWatchAccessCheckBox)
|
||||
watchBox.addNewPanelForCheckBox(myWatchModificationCheckBox)
|
||||
|
||||
val mainPanel = JPanel(BorderLayout())
|
||||
val innerPanel = JPanel(BorderLayout())
|
||||
innerPanel.add(watchBox, BorderLayout.CENTER)
|
||||
innerPanel.add(Box.createHorizontalStrut(3), BorderLayout.WEST)
|
||||
innerPanel.add(Box.createHorizontalStrut(3), BorderLayout.EAST)
|
||||
mainPanel.add(innerPanel, BorderLayout.NORTH)
|
||||
mainPanel.border = IdeBorderFactory.createTitledBorder(DebuggerBundle.message("label.group.watch.events"), true)
|
||||
return mainPanel
|
||||
}
|
||||
|
||||
override fun loadFrom(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>) {
|
||||
myWatchInitializationCheckBox.isSelected = breakpoint.properties.WATCH_INITIALIZATION
|
||||
myWatchAccessCheckBox.isSelected = breakpoint.properties.WATCH_ACCESS
|
||||
myWatchModificationCheckBox.isSelected = breakpoint.properties.WATCH_MODIFICATION
|
||||
}
|
||||
|
||||
override fun saveTo(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>) {
|
||||
var changed = breakpoint.properties.WATCH_ACCESS != myWatchAccessCheckBox.isSelected
|
||||
breakpoint.properties.WATCH_ACCESS = myWatchAccessCheckBox.isSelected
|
||||
|
||||
changed = breakpoint.properties.WATCH_MODIFICATION != myWatchModificationCheckBox.isSelected || changed
|
||||
breakpoint.properties.WATCH_MODIFICATION = myWatchModificationCheckBox.isSelected
|
||||
|
||||
changed = breakpoint.properties.WATCH_INITIALIZATION != myWatchInitializationCheckBox.isSelected || changed
|
||||
breakpoint.properties.WATCH_INITIALIZATION = myWatchInitializationCheckBox.isSelected
|
||||
|
||||
if (changed) {
|
||||
(breakpoint as XBreakpointBase<*, *, *>).fireBreakpointChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.ui.breakpoints.Breakpoint
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointManager
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointWithHighlighter
|
||||
import com.intellij.debugger.ui.breakpoints.JavaBreakpointType
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpointType
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.dialog.AddFieldBreakpointDialog
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.KtDeclarationContainer
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import javax.swing.JComponent
|
||||
|
||||
class KotlinFieldBreakpointType : JavaBreakpointType<KotlinPropertyBreakpointProperties>, XLineBreakpointType<KotlinPropertyBreakpointProperties>(
|
||||
"kotlin-field", KotlinBundle.message("debugger.field.watchpoints.tab.title")
|
||||
) {
|
||||
override fun createJavaBreakpoint(project: Project, breakpoint: XBreakpoint<KotlinPropertyBreakpointProperties>): Breakpoint<KotlinPropertyBreakpointProperties> {
|
||||
return KotlinFieldBreakpoint(project, breakpoint)
|
||||
}
|
||||
|
||||
override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean {
|
||||
return canPutAt(file, line, project, this::class.java)
|
||||
}
|
||||
|
||||
override fun getPriority() = 120
|
||||
|
||||
override fun createBreakpointProperties(file: VirtualFile, line: Int): KotlinPropertyBreakpointProperties? {
|
||||
return KotlinPropertyBreakpointProperties()
|
||||
}
|
||||
|
||||
override fun addBreakpoint(project: Project, parentComponent: JComponent?): XLineBreakpoint<KotlinPropertyBreakpointProperties>? {
|
||||
var result: XLineBreakpoint<KotlinPropertyBreakpointProperties>? = null
|
||||
|
||||
val dialog = object : AddFieldBreakpointDialog(project) {
|
||||
override fun validateData(): Boolean {
|
||||
val className = className
|
||||
if (className.isEmpty()) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.class.name.not.specified"))
|
||||
return false
|
||||
}
|
||||
|
||||
val psiClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project))
|
||||
if (psiClass !is KtLightClass) {
|
||||
reportError(project, "Couldn't find '$className' class")
|
||||
return false
|
||||
}
|
||||
|
||||
val fieldName = fieldName
|
||||
if (fieldName.isEmpty()) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.field.name.not.specified"))
|
||||
return false
|
||||
}
|
||||
|
||||
result = when (psiClass) {
|
||||
is KtLightClassForFacade -> {
|
||||
psiClass.files.asSequence().mapNotNull { createBreakpointIfPropertyExists(it, it, className, fieldName) }.firstOrNull()
|
||||
}
|
||||
is KtLightClassForSourceDeclaration -> {
|
||||
val jetClass = psiClass.kotlinOrigin
|
||||
createBreakpointIfPropertyExists(jetClass, jetClass.containingKtFile, className, fieldName)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.field.not.found", className, fieldName, fieldName))
|
||||
}
|
||||
|
||||
return result != null
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createBreakpointIfPropertyExists(
|
||||
declaration: KtDeclarationContainer,
|
||||
file: KtFile,
|
||||
className: String,
|
||||
fieldName: String
|
||||
): XLineBreakpoint<KotlinPropertyBreakpointProperties>? {
|
||||
val project = file.project
|
||||
val property = declaration.declarations.firstOrNull { it is KtProperty && it.name == fieldName } ?: return null
|
||||
|
||||
val document = PsiDocumentManager.getInstance(project).getDocument(file) ?: return null
|
||||
val line = document.getLineNumber(property.textOffset)
|
||||
return runWriteAction {
|
||||
XDebuggerManager.getInstance(project).breakpointManager.addLineBreakpoint(
|
||||
this,
|
||||
file.virtualFile.url,
|
||||
line,
|
||||
KotlinPropertyBreakpointProperties(fieldName, className)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportError(project: Project, message: String) {
|
||||
Messages.showMessageDialog(project, message, DebuggerBundle.message("add.field.breakpoint.dialog.title"), Messages.getErrorIcon())
|
||||
}
|
||||
|
||||
override fun isAddBreakpointButtonVisible() = true
|
||||
|
||||
override fun getMutedEnabledIcon() = AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
|
||||
override fun getDisabledIcon() = AllIcons.Debugger.Db_disabled_field_breakpoint
|
||||
|
||||
override fun getEnabledIcon() = AllIcons.Debugger.Db_field_breakpoint
|
||||
|
||||
override fun getMutedDisabledIcon() = AllIcons.Debugger.Db_muted_disabled_field_breakpoint
|
||||
|
||||
override fun canBeHitInOtherPlaces() = true
|
||||
|
||||
override fun getShortText(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>): String? {
|
||||
val properties = breakpoint.properties
|
||||
val className = properties.myClassName
|
||||
return if (!className.isEmpty()) className + "." + properties.myFieldName else properties.myFieldName
|
||||
}
|
||||
|
||||
override fun createProperties(): KotlinPropertyBreakpointProperties? {
|
||||
return KotlinPropertyBreakpointProperties()
|
||||
}
|
||||
|
||||
override fun createCustomPropertiesPanel(): XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>? {
|
||||
return KotlinFieldBreakpointPropertiesPanel()
|
||||
}
|
||||
|
||||
override fun getDisplayText(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>): String? {
|
||||
val kotlinBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint) as? BreakpointWithHighlighter
|
||||
return if (kotlinBreakpoint != null) {
|
||||
kotlinBreakpoint.description
|
||||
}
|
||||
else {
|
||||
super.getDisplayText(breakpoint)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEditorsProvider() = null
|
||||
|
||||
override fun createCustomRightPropertiesPanel(project: Project): XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>? {
|
||||
return KotlinBreakpointFiltersPanel(project)
|
||||
}
|
||||
|
||||
override fun isSuspendThreadSupported() = true
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
|
||||
class KotlinLineBreakpoint(
|
||||
project: Project?,
|
||||
xBreakpoint: XBreakpoint<out XBreakpointProperties<*>>?
|
||||
) : LineBreakpoint<JavaLineBreakpointProperties>(project, xBreakpoint) {
|
||||
override fun processClassPrepare(debugProcess: DebugProcess?, classType: ReferenceType?) {
|
||||
val sourcePosition = xBreakpoint?.sourcePosition
|
||||
|
||||
if (classType != null && sourcePosition != null) {
|
||||
if (!hasTargetLine(classType, sourcePosition)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
super.processClassPrepare(debugProcess, classType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if `classType` definitely does not contain a location for a given `sourcePosition`.
|
||||
*/
|
||||
private fun hasTargetLine(classType: ReferenceType, sourcePosition: XSourcePosition): Boolean {
|
||||
val allLineLocations = DebuggerUtilsEx.allLineLocations(classType) ?: return true
|
||||
|
||||
if (classType.virtualMachine().isDexDebug()) {
|
||||
return true
|
||||
}
|
||||
|
||||
val fileName = sourcePosition.file.name
|
||||
val lineNumber = sourcePosition.line + 1
|
||||
|
||||
for (location in allLineLocations) {
|
||||
try {
|
||||
val kotlinFileName = location.sourceName(KOTLIN_STRATA_NAME)
|
||||
val kotlinLineNumber = location.lineNumber(KOTLIN_STRATA_NAME)
|
||||
if (kotlinFileName == fileName && kotlinLineNumber == lineNumber) {
|
||||
return true
|
||||
}
|
||||
} catch (e: AbsentInformationException) {
|
||||
if (location.sourceName() == fileName && location.lineNumber() == lineNumber) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.breakpoints;
|
||||
|
||||
import com.intellij.debugger.SourcePosition;
|
||||
import com.intellij.debugger.ui.breakpoints.Breakpoint;
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointManager;
|
||||
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType;
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager;
|
||||
import org.jetbrains.kotlin.psi.KtClassInitializer;
|
||||
import org.jetbrains.kotlin.psi.KtFunction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KotlinLineBreakpointType extends JavaLineBreakpointType {
|
||||
public KotlinLineBreakpointType() {
|
||||
super("kotlin-line", "Kotlin Line Breakpoints");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Breakpoint<JavaLineBreakpointProperties> createJavaBreakpoint(
|
||||
Project project, XBreakpoint<JavaLineBreakpointProperties> breakpoint
|
||||
) {
|
||||
return new KotlinLineBreakpoint(project, breakpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesPosition(@NotNull LineBreakpoint<?> breakpoint, @NotNull SourcePosition position) {
|
||||
JavaBreakpointProperties properties = getProperties(breakpoint);
|
||||
if (properties == null || properties instanceof JavaLineBreakpointProperties) {
|
||||
if (position instanceof KotlinPositionManager.KotlinReentrantSourcePosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (properties != null && ((JavaLineBreakpointProperties) properties).getLambdaOrdinal() == null) return true;
|
||||
|
||||
PsiElement containingMethod = getContainingMethod(breakpoint);
|
||||
if (containingMethod == null) return false;
|
||||
return inTheMethod(position, containingMethod);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiElement getContainingMethod(@NotNull LineBreakpoint<?> breakpoint) {
|
||||
SourcePosition position = breakpoint.getSourcePosition();
|
||||
if (position == null) return null;
|
||||
|
||||
JavaBreakpointProperties properties = getProperties(breakpoint);
|
||||
if (properties instanceof JavaLineBreakpointProperties) {
|
||||
Integer ordinal = ((JavaLineBreakpointProperties) properties).getLambdaOrdinal();
|
||||
PsiElement lambda = getLambdaByOrdinal(position, ordinal);
|
||||
if (lambda != null) return lambda;
|
||||
}
|
||||
|
||||
return getContainingMethod(position.getElementAt());
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static JavaBreakpointProperties getProperties(@NotNull LineBreakpoint<?> breakpoint) {
|
||||
XBreakpoint<?> xBreakpoint = breakpoint.getXBreakpoint();
|
||||
return xBreakpoint != null ? (JavaBreakpointProperties) xBreakpoint.getProperties() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static KtFunction getLambdaByOrdinal(SourcePosition position, Integer ordinal) {
|
||||
if (ordinal != null && ordinal >= 0) {
|
||||
List<KtFunction> lambdas = BreakpointTypeUtilsKt.getLambdasAtLineIfAny(position);
|
||||
if (lambdas.size() > ordinal) {
|
||||
return lambdas.get(ordinal);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement getContainingMethod(@Nullable PsiElement elem) {
|
||||
//noinspection unchecked
|
||||
return PsiTreeUtil.getParentOfType(elem, KtFunction.class, KtClassInitializer.class);
|
||||
}
|
||||
|
||||
public static boolean inTheMethod(@NotNull SourcePosition pos, @NotNull PsiElement method) {
|
||||
PsiElement elem = pos.getElementAt();
|
||||
if (elem == null) return false;
|
||||
return Comparing.equal(getContainingMethod(elem), method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
|
||||
return BreakpointTypeUtilsKt.canPutAt(file, line, project, getClass());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<JavaBreakpointVariant> computeVariants(@NotNull Project project, @NotNull XSourcePosition position) {
|
||||
return BreakpointTypeUtilsKt.computeVariants(project, position, this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TextRange getHighlightRange(XLineBreakpoint<JavaLineBreakpointProperties> breakpoint) {
|
||||
JavaLineBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
Integer ordinal = properties.getLambdaOrdinal();
|
||||
if (ordinal != null) {
|
||||
Breakpoint javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint);
|
||||
if (javaBreakpoint instanceof LineBreakpoint) {
|
||||
SourcePosition position = ((LineBreakpoint) javaBreakpoint).getSourcePosition();
|
||||
if (position != null) {
|
||||
KtFunction lambda = getLambdaByOrdinal(position, ordinal);
|
||||
if (lambda != null) {
|
||||
return lambda.getTextRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties
|
||||
|
||||
class KotlinPropertyBreakpointProperties(
|
||||
@Attribute var myFieldName: String = "",
|
||||
@Attribute var myClassName: String = ""
|
||||
): JavaBreakpointProperties<KotlinPropertyBreakpointProperties>() {
|
||||
var WATCH_MODIFICATION: Boolean = true
|
||||
var WATCH_ACCESS: Boolean = false
|
||||
var WATCH_INITIALIZATION: Boolean = false
|
||||
|
||||
override fun getState() = this
|
||||
|
||||
override fun loadState(state: KotlinPropertyBreakpointProperties) {
|
||||
super.loadState(state)
|
||||
|
||||
WATCH_MODIFICATION = state.WATCH_MODIFICATION
|
||||
WATCH_ACCESS = state.WATCH_ACCESS
|
||||
WATCH_INITIALIZATION = state.WATCH_INITIALIZATION
|
||||
myFieldName = state.myFieldName
|
||||
myClassName = state.myClassName
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.findElementAtLine
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
import java.util.*
|
||||
|
||||
fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass: Class<*>): Boolean {
|
||||
val psiFile = PsiManager.getInstance(project).findFile(file)
|
||||
|
||||
if (psiFile == null || psiFile.virtualFile?.fileType != KotlinFileType.INSTANCE) {
|
||||
return false
|
||||
}
|
||||
|
||||
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
|
||||
|
||||
var result: Class<*>? = null
|
||||
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun (el: PsiElement): Boolean {
|
||||
// avoid comments
|
||||
if (el is PsiWhiteSpace || PsiTreeUtil.getParentOfType(el, PsiComment::class.java, false) != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
var element = el
|
||||
var parent = element.parent
|
||||
while (parent != null) {
|
||||
val offset = parent.textOffset
|
||||
if (offset >= 0 && document.getLineNumber(offset) != line) break
|
||||
|
||||
element = parent
|
||||
parent = element.parent
|
||||
}
|
||||
|
||||
if (element is KtProperty || element is KtParameter) {
|
||||
result = if ((element is KtParameter && element.hasValOrVar()) || (element is KtProperty && !element.isLocal)) {
|
||||
KotlinFieldBreakpointType::class.java
|
||||
}
|
||||
else {
|
||||
KotlinLineBreakpointType::class.java
|
||||
}
|
||||
return false
|
||||
}
|
||||
else {
|
||||
result = KotlinLineBreakpointType::class.java
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result == breakpointTypeClass
|
||||
}
|
||||
|
||||
fun computeVariants(
|
||||
project: Project,
|
||||
position: XSourcePosition,
|
||||
kotlinBreakpointType: KotlinLineBreakpointType
|
||||
): List<JavaLineBreakpointType.JavaBreakpointVariant> {
|
||||
val file = PsiManager.getInstance(project).findFile(position.file) as? KtFile ?: return emptyList()
|
||||
|
||||
val pos = SourcePosition.createFromLine(file, position.line)
|
||||
val lambdas = getLambdasAtLineIfAny(pos)
|
||||
if (lambdas.isEmpty()) return emptyList()
|
||||
|
||||
val result = LinkedList<JavaLineBreakpointType.JavaBreakpointVariant>()
|
||||
|
||||
val elementAt = pos.elementAt.parentsWithSelf.firstIsInstance<KtElement>()
|
||||
val mainMethod = KotlinLineBreakpointType.getContainingMethod(elementAt)
|
||||
if (mainMethod != null) {
|
||||
result.add(
|
||||
kotlinBreakpointType.LineJavaBreakpointVariant(
|
||||
position,
|
||||
CodeInsightUtils.getTopmostElementAtOffset(elementAt, pos.offset),
|
||||
-1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
lambdas.forEachIndexed { ordinal, lambda ->
|
||||
val positionImpl = XSourcePositionImpl.createByElement(lambda.bodyExpression)
|
||||
|
||||
if (positionImpl != null) {
|
||||
result.add(kotlinBreakpointType.LambdaJavaBreakpointVariant(positionImpl, lambda, ordinal))
|
||||
}
|
||||
}
|
||||
|
||||
result.add(kotlinBreakpointType.JavaBreakpointVariant(position))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun getLambdasAtLineIfAny(sourcePosition: SourcePosition): List<KtFunction> {
|
||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||
val lineNumber = sourcePosition.line
|
||||
return getLambdasAtLineIfAny(file, lineNumber)
|
||||
}
|
||||
|
||||
fun getLambdasAtLineIfAny(file: KtFile, line: Int): List<KtFunction> {
|
||||
val lineElement = findElementAtLine(file, line) as? KtElement ?: return emptyList()
|
||||
|
||||
val start = lineElement.startOffset
|
||||
val end = lineElement.endOffset
|
||||
|
||||
val allLiterals = CodeInsightUtils.
|
||||
findElementsOfClassInRange(file, start, end, KtFunction::class.java)
|
||||
.filterIsInstance<KtFunction>()
|
||||
// filter function literals and functional expressions
|
||||
.filter { it is KtFunctionLiteral || it.name == null }
|
||||
.toSet()
|
||||
|
||||
return allLiterals.filter {
|
||||
val statement = it.bodyBlockExpression?.statements?.firstOrNull() ?: it
|
||||
statement.getLineNumber() == line && statement.getLineNumber(false) == line
|
||||
}
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.breakpoints.dialog.AddFieldBreakpointDialog">
|
||||
<grid id="dbe86" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="6">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="74" y="134" width="245" height="152"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="9636d" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myClassChooser">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="c2ef3" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myFieldChooser">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<xy id="3cfba" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="2" fill="1" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="-1" height="1"/>
|
||||
<maximum-size width="-1" height="1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="bevel-raised"/>
|
||||
<children/>
|
||||
</xy>
|
||||
<vspacer id="339be">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="e8c64" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="label.add.field.breakpoint.dialog.field.name"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="c159b" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="label.add.field.breakpoint.dialog.fq.name"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.breakpoints.dialog;
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.ide.util.MemberChooser;
|
||||
import com.intellij.ide.util.TreeClassChooser;
|
||||
import com.intellij.ide.util.TreeClassChooserFactory;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject;
|
||||
import org.jetbrains.kotlin.idea.core.util.UiUtilsKt;
|
||||
import org.jetbrains.kotlin.psi.KtProperty;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AddFieldBreakpointDialog extends DialogWrapper {
|
||||
private final Project myProject;
|
||||
private JPanel myPanel;
|
||||
private TextFieldWithBrowseButton myFieldChooser;
|
||||
private TextFieldWithBrowseButton myClassChooser;
|
||||
|
||||
public AddFieldBreakpointDialog(Project project) {
|
||||
super(project, true);
|
||||
myProject = project;
|
||||
setTitle(DebuggerBundle.message("add.field.breakpoint.dialog.title"));
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
UiUtilsKt.onTextChange(
|
||||
myClassChooser.getTextField(),
|
||||
(DocumentEvent e) -> {
|
||||
updateUI();
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
);
|
||||
|
||||
myClassChooser.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
PsiClass currentClass = getSelectedClass();
|
||||
TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject).createAllProjectScopeChooser(
|
||||
DebuggerBundle.message("add.field.breakpoint.dialog.classchooser.title"));
|
||||
if (currentClass != null) {
|
||||
PsiFile containingFile = currentClass.getContainingFile();
|
||||
if (containingFile != null) {
|
||||
PsiDirectory containingDirectory = containingFile.getContainingDirectory();
|
||||
if (containingDirectory != null) {
|
||||
chooser.selectDirectory(containingDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
chooser.showDialog();
|
||||
PsiClass selectedClass = chooser.getSelected();
|
||||
if (selectedClass != null) {
|
||||
myClassChooser.setText(selectedClass.getQualifiedName());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myFieldChooser.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
PsiClass selectedClass = getSelectedClass();
|
||||
DescriptorMemberChooserObject[] properties = FieldBreakpointDialogUtilKt.collectProperties(selectedClass);
|
||||
MemberChooser<DescriptorMemberChooserObject> chooser = new MemberChooser<DescriptorMemberChooserObject>(properties, false, false, myProject);
|
||||
chooser.setTitle(DebuggerBundle.message("add.field.breakpoint.dialog.field.chooser.title", properties.length));
|
||||
chooser.setCopyJavadocVisible(false);
|
||||
chooser.show();
|
||||
List<DescriptorMemberChooserObject> selectedElements = chooser.getSelectedElements();
|
||||
if (selectedElements != null && selectedElements.size() == 1) {
|
||||
KtProperty field = (KtProperty) selectedElements.get(0).getElement();
|
||||
myFieldChooser.setText(field.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
myFieldChooser.setEnabled(false);
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
PsiClass selectedClass = getSelectedClass();
|
||||
myFieldChooser.setEnabled(selectedClass != null);
|
||||
}
|
||||
|
||||
private PsiClass getSelectedClass() {
|
||||
PsiManager psiManager = PsiManager.getInstance(myProject);
|
||||
String classQName = myClassChooser.getText();
|
||||
if (classQName == null || classQName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return JavaPsiFacade.getInstance(psiManager.getProject()).findClass(classQName, GlobalSearchScope.allScope(myProject));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myClassChooser.getTextField();
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return myClassChooser.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return "#com.intellij.debugger.ui.breakpoints.BreakpointsConfigurationDialogFactory.BreakpointsConfigurationDialog.AddFieldBreakpointDialog";
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return myFieldChooser.getText();
|
||||
}
|
||||
|
||||
protected abstract boolean validateData();
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
if (validateData()) {
|
||||
super.doOKAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints.dialog
|
||||
|
||||
import com.intellij.psi.PsiClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
|
||||
fun PsiClass.collectProperties(): Array<DescriptorMemberChooserObject> {
|
||||
if (this is KtLightClassForFacade) {
|
||||
val result = arrayListOf<DescriptorMemberChooserObject>()
|
||||
this.files.forEach {
|
||||
it.declarations.filterIsInstance<KtProperty>().forEach {
|
||||
result.add(DescriptorMemberChooserObject(it, it.unsafeResolveToDescriptor()))
|
||||
}
|
||||
}
|
||||
return result.toTypedArray()
|
||||
}
|
||||
if (this is KtLightClass) {
|
||||
val origin = this.kotlinOrigin
|
||||
if (origin != null) {
|
||||
return origin.declarations.filterIsInstance<KtProperty>().map {
|
||||
DescriptorMemberChooserObject(it, it.unsafeResolveToDescriptor())
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.sun.jdi.*
|
||||
import com.sun.tools.jdi.LocalVariableImpl
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
|
||||
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.idea.core.KotlinFileTypeFactory
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineEndOffset
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import java.util.*
|
||||
|
||||
fun Location.isInKotlinSources(): Boolean {
|
||||
val declaringType = declaringType()
|
||||
val fileExtension = declaringType.safeSourceName()?.substringAfterLast('.')?.toLowerCase() ?: ""
|
||||
return fileExtension in KotlinFileTypeFactory.KOTLIN_EXTENSIONS || declaringType.containsKotlinStrata()
|
||||
}
|
||||
|
||||
fun ReferenceType.containsKotlinStrata() = availableStrata().contains(KOTLIN_STRATA_NAME)
|
||||
|
||||
fun isInsideInlineArgument(
|
||||
inlineArgument: KtFunction,
|
||||
location: Location,
|
||||
debugProcess: DebugProcessImpl,
|
||||
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(inlineArgument).bindingContext
|
||||
): Boolean {
|
||||
val visibleVariables = location.visibleVariables(debugProcess)
|
||||
val markerLocalVariables = visibleVariables.filter { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) }
|
||||
|
||||
val context = KotlinDebuggerCaches.getOrCreateTypeMapper(inlineArgument).bindingContext
|
||||
val lambdaOrdinal = runReadAction { lambdaOrdinalByArgument(inlineArgument, context) }
|
||||
val functionName = runReadAction { functionNameByArgument(inlineArgument, context) }
|
||||
|
||||
return markerLocalVariables
|
||||
.map { it.name().drop(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT.length) }
|
||||
.any { variableName ->
|
||||
if (variableName.startsWith("-")) {
|
||||
val lambdaClassName = asmTypeForAnonymousClass(bindingContext, inlineArgument)
|
||||
.internalName.substringAfterLast("/")
|
||||
|
||||
variableName == "-$functionName-$lambdaClassName"
|
||||
} else {
|
||||
// For Kotlin up to 1.3.10
|
||||
lambdaOrdinalByLocalVariable(variableName) == lambdaOrdinal
|
||||
&& functionNameByLocalVariable(variableName) == functionName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any> DebugProcessImpl.invokeInManagerThread(f: (DebuggerContextImpl) -> T?): T? {
|
||||
var result: T? = null
|
||||
val command: DebuggerCommandImpl = object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
result = runReadAction { f(debuggerContext) }
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
DebuggerManagerThreadImpl.isManagerThread() ->
|
||||
managerThread.invoke(command)
|
||||
else ->
|
||||
managerThread.invokeAndWait(command)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun lambdaOrdinalByArgument(elementAt: KtFunction, context: BindingContext): Int {
|
||||
val type = CodegenBinding.asmTypeForAnonymousClass(context, elementAt)
|
||||
return type.className.substringAfterLast("$").toInt()
|
||||
}
|
||||
|
||||
private fun functionNameByArgument(elementAt: KtFunction, context: BindingContext): String {
|
||||
val inlineArgumentDescriptor = InlineUtil.getInlineArgumentDescriptor(elementAt, context)
|
||||
return inlineArgumentDescriptor?.containingDeclaration?.name?.asString() ?: "unknown"
|
||||
}
|
||||
|
||||
private fun Location.visibleVariables(debugProcess: DebugProcessImpl): List<LocalVariable> {
|
||||
val stackFrame = MockStackFrame(this, debugProcess.virtualMachineProxy.virtualMachine)
|
||||
return stackFrame.visibleVariables()
|
||||
}
|
||||
|
||||
private fun lambdaOrdinalByLocalVariable(name: String): Int {
|
||||
try {
|
||||
val nameWithoutPrefix = name.removePrefix(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT)
|
||||
return Integer.parseInt(nameWithoutPrefix.substringBefore("$", nameWithoutPrefix))
|
||||
}
|
||||
catch(e: NumberFormatException) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun functionNameByLocalVariable(name: String): String {
|
||||
val nameWithoutPrefix = name.removePrefix(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT)
|
||||
return nameWithoutPrefix.substringAfterLast("$", "unknown")
|
||||
}
|
||||
|
||||
private class MockStackFrame(private val location: Location, private val vm: VirtualMachine) : StackFrame {
|
||||
private var visibleVariables: Map<String, LocalVariable>? = null
|
||||
|
||||
override fun location() = location
|
||||
override fun thread() = null
|
||||
override fun thisObject() = null
|
||||
|
||||
private fun createVisibleVariables() {
|
||||
if (visibleVariables == null) {
|
||||
val allVariables = location.method().safeVariables() ?: emptyList()
|
||||
val map = HashMap<String, LocalVariable>(allVariables.size)
|
||||
|
||||
for (allVariable in allVariables) {
|
||||
val variable = allVariable as LocalVariableImpl
|
||||
val name = variable.name()
|
||||
if (variable.isVisible(this)) {
|
||||
map.put(name, variable)
|
||||
}
|
||||
}
|
||||
visibleVariables = map
|
||||
}
|
||||
}
|
||||
|
||||
override fun visibleVariables(): List<LocalVariable> {
|
||||
createVisibleVariables()
|
||||
val mapAsList = ArrayList(visibleVariables!!.values)
|
||||
Collections.sort(mapAsList)
|
||||
return mapAsList
|
||||
}
|
||||
|
||||
override fun visibleVariableByName(name: String): LocalVariable? {
|
||||
createVisibleVariables()
|
||||
return visibleVariables!![name]
|
||||
}
|
||||
|
||||
override fun getValue(variable: LocalVariable) = null
|
||||
override fun getValues(variables: List<LocalVariable>): Map<LocalVariable, Value> = emptyMap()
|
||||
override fun setValue(variable: LocalVariable, value: Value) {
|
||||
}
|
||||
|
||||
override fun getArgumentValues(): List<Value> = emptyList()
|
||||
override fun virtualMachine() = vm
|
||||
}
|
||||
|
||||
private const val DO_RESUME_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Throwable;)Ljava/lang/Object;"
|
||||
private const val INVOKE_SUSPEND_SIGNATURE = "(Ljava/lang/Object;)Ljava/lang/Object;"
|
||||
|
||||
fun isInSuspendMethod(location: Location): Boolean {
|
||||
val method = location.method()
|
||||
val signature = method.signature()
|
||||
|
||||
for (continuationAsmType in continuationAsmTypes()) {
|
||||
if (signature.contains(continuationAsmType.toString()) ||
|
||||
(method.name() == DO_RESUME_METHOD_NAME && signature == DO_RESUME_SIGNATURE) ||
|
||||
(method.name() == INVOKE_SUSPEND_METHOD_NAME && signature == INVOKE_SUSPEND_SIGNATURE)
|
||||
) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun suspendFunctionFirstLineLocation(location: Location): Int? {
|
||||
if (!isInSuspendMethod(location)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val lineNumber = location.method().location()?.lineNumber()
|
||||
if (lineNumber == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return lineNumber
|
||||
}
|
||||
|
||||
fun isOnSuspendReturnOrReenter(location: Location): Boolean {
|
||||
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return false
|
||||
return suspendStartLineNumber == location.lineNumber()
|
||||
}
|
||||
|
||||
fun isLastLineLocationInMethod(location: Location): Boolean {
|
||||
val knownLines = location.method().safeAllLineLocations().map { it.lineNumber() }.filter { it != -1 }
|
||||
if (knownLines.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return knownLines.max() == location.lineNumber()
|
||||
}
|
||||
|
||||
fun isOneLineMethod(location: Location): Boolean {
|
||||
val allLineLocations = location.method().safeAllLineLocations()
|
||||
val firstLine = allLineLocations.firstOrNull()?.lineNumber()
|
||||
val lastLine = allLineLocations.lastOrNull()?.lineNumber()
|
||||
|
||||
return firstLine != null && firstLine == lastLine
|
||||
}
|
||||
|
||||
fun findElementAtLine(file: KtFile, line: Int): PsiElement? {
|
||||
val lineStartOffset = file.getLineStartOffset(line) ?: return null
|
||||
val lineEndOffset = file.getLineEndOffset(line) ?: return null
|
||||
|
||||
var topMostElement: PsiElement? = null
|
||||
var elementAt: PsiElement?
|
||||
for (offset in lineStartOffset until lineEndOffset) {
|
||||
elementAt = file.findElementAt(offset)
|
||||
if (elementAt != null) {
|
||||
topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, offset)
|
||||
if (topMostElement is KtElement) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return topMostElement
|
||||
}
|
||||
|
||||
fun findCallByEndToken(element: PsiElement): KtCallExpression? {
|
||||
if (element is KtElement) return null
|
||||
|
||||
return when (element.node.elementType) {
|
||||
KtTokens.RPAR -> (element.parent as? KtValueArgumentList)?.parent as? KtCallExpression
|
||||
KtTokens.RBRACE -> {
|
||||
val braceParent = CodeInsightUtils.getTopParentWithEndOffset(element, KtCallExpression::class.java)
|
||||
when (braceParent) {
|
||||
is KtCallExpression -> braceParent
|
||||
is KtLambdaArgument -> braceParent.parent as? KtCallExpression
|
||||
is KtValueArgument -> (braceParent.parent as? KtValueArgumentList)?.parent as? KtCallExpression
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val DebuggerContextImpl.canRunEvaluation: Boolean
|
||||
get() = debugProcess?.canRunEvaluation ?: false
|
||||
|
||||
val DebugProcessImpl.canRunEvaluation: Boolean
|
||||
get() = suspendManager.pausedContext != null
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerInvocationUtil
|
||||
import com.intellij.debugger.engine.ContextUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.ui.EditorEvaluationCommand
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.sun.jdi.ClassType
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.debugger.getClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
abstract class KotlinRuntimeTypeEvaluator(
|
||||
editor: Editor?,
|
||||
expression: KtExpression,
|
||||
context: DebuggerContextImpl,
|
||||
indicator: ProgressIndicator
|
||||
) : EditorEvaluationCommand<KotlinType>(editor, expression, context, indicator) {
|
||||
|
||||
override fun threadAction() {
|
||||
var type: KotlinType? = null
|
||||
try {
|
||||
type = evaluate()
|
||||
} catch (ignored: ProcessCanceledException) {
|
||||
} catch (ignored: EvaluateException) {
|
||||
} finally {
|
||||
typeCalculationFinished(type)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun typeCalculationFinished(type: KotlinType?)
|
||||
|
||||
override fun evaluate(evaluationContext: EvaluationContextImpl): KotlinType? {
|
||||
val project = evaluationContext.project
|
||||
|
||||
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project) {
|
||||
val codeFragment = KtPsiFactory(myElement.project)
|
||||
.createExpressionCodeFragment(myElement.text, myElement.containingFile.context)
|
||||
|
||||
val codeFragmentFactory = DebuggerUtilsEx.getCodeFragmentFactory(codeFragment.context, KotlinFileType.INSTANCE)
|
||||
codeFragmentFactory.evaluatorBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
|
||||
}
|
||||
|
||||
val value = evaluator.evaluate(evaluationContext)
|
||||
if (value != null) {
|
||||
return runReadAction { getCastableRuntimeType(evaluationContext.debugProcess.searchScope, value) }
|
||||
}
|
||||
|
||||
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.surrounded.expression.null"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getCastableRuntimeType(scope: GlobalSearchScope, value: Value): KotlinType? {
|
||||
val myValue = value.asValue()
|
||||
var psiClass = myValue.asmType.getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
|
||||
val type = value.type()
|
||||
if (type is ClassType) {
|
||||
val superclass = type.superclass()
|
||||
if (superclass != null && CommonClassNames.JAVA_LANG_OBJECT != superclass.name()) {
|
||||
psiClass = AsmType.getType(superclass.signature()).getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
}
|
||||
|
||||
for (interfaceType in type.interfaces()) {
|
||||
psiClass = AsmType.getType(interfaceType.signature()).getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.filter
|
||||
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.ui.classFilter.ClassFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
|
||||
private val KOTLIN_STDLIB_FILTER = "kotlin.*"
|
||||
|
||||
fun addKotlinStdlibDebugFilterIfNeeded() {
|
||||
if (!KotlinDebuggerSettings.getInstance().DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED) {
|
||||
val settings = DebuggerSettings.getInstance()!!
|
||||
val newFilters = (settings.steppingFilters + ClassFilter(KOTLIN_STDLIB_FILTER))
|
||||
|
||||
settings.steppingFilters = newFilters
|
||||
|
||||
KotlinDebuggerSettings.getInstance().DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED = true
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.filter
|
||||
|
||||
import com.intellij.ui.classFilter.ClassFilter
|
||||
import com.intellij.ui.classFilter.DebuggerClassFilterProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
|
||||
private val FILTERS = listOf(
|
||||
ClassFilter("kotlin.jvm*"),
|
||||
ClassFilter("kotlin.reflect*"),
|
||||
ClassFilter("kotlin.NoWhenBranchMatchedException"),
|
||||
ClassFilter("kotlin.TypeCastException"),
|
||||
ClassFilter("kotlin.KotlinNullPointerException")
|
||||
)
|
||||
|
||||
class KotlinDebuggerInternalClassesFilterProvider : DebuggerClassFilterProvider {
|
||||
override fun getFilters(): List<ClassFilter>? {
|
||||
return if (KotlinDebuggerSettings.getInstance().DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES) FILTERS else listOf()
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.filter
|
||||
|
||||
import com.intellij.debugger.engine.SyntheticTypeComponentProvider
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.safeAllLineLocations
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import kotlin.jvm.internal.FunctionReference
|
||||
import kotlin.jvm.internal.PropertyReference
|
||||
|
||||
class KotlinSyntheticTypeComponentProvider: SyntheticTypeComponentProvider {
|
||||
override fun isSynthetic(typeComponent: TypeComponent?): Boolean {
|
||||
if (typeComponent !is Method) return false
|
||||
|
||||
val containingType = typeComponent.declaringType()
|
||||
val typeName = containingType.name()
|
||||
if (!FqNameUnsafe.isValid(typeName)) return false
|
||||
|
||||
// TODO: this is most likely not necessary since KT-28453 is fixed, but still can be useful when debugging old compiled code
|
||||
if (containingType.isCallableReferenceSyntheticClass()) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeComponent.isDelegateToDefaultInterfaceImpl()) return true
|
||||
|
||||
if (typeComponent.location()?.lineNumber() != 1) return false
|
||||
|
||||
if (typeComponent.allLineLocations().any { it.lineNumber() != 1 }) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !typeComponent.declaringType().allLineLocations().any { it.lineNumber() != 1 }
|
||||
}
|
||||
catch(e: AbsentInformationException) {
|
||||
return false
|
||||
}
|
||||
catch(e: UnsupportedOperationException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private tailrec fun ReferenceType?.isCallableReferenceSyntheticClass(): Boolean {
|
||||
if (this !is ClassType) return false
|
||||
val superClass = this.superclass() ?: return false
|
||||
val superClassName = superClass.name()
|
||||
if (superClassName == PropertyReference::class.java.name || superClassName == FunctionReference::class.java.name) {
|
||||
return true
|
||||
}
|
||||
|
||||
// The direct supertype may be PropertyReference0 or something
|
||||
return if (superClassName.startsWith("kotlin.jvm.internal."))
|
||||
superClass.isCallableReferenceSyntheticClass()
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
private fun Method.isDelegateToDefaultInterfaceImpl(): Boolean {
|
||||
if (safeAllLineLocations().size != 1) return false
|
||||
if (!virtualMachine().canGetBytecodes()) return false
|
||||
|
||||
if (!hasOnlyInvokeStatic(this)) return false
|
||||
|
||||
return hasInterfaceWithImplementation(this)
|
||||
}
|
||||
|
||||
private val LOAD_INSTRUCTIONS_WITH_INDEX = Opcodes.ILOAD.toByte()..Opcodes.ALOAD.toByte()
|
||||
private val LOAD_INSTRUCTIONS = (Opcodes.ALOAD + 1).toByte()..(Opcodes.IALOAD - 1).toByte()
|
||||
|
||||
private val RETURN_INSTRUCTIONS = Opcodes.IRETURN.toByte()..Opcodes.RETURN.toByte()
|
||||
|
||||
// Check that method contains only load and invokeStatic instructions. Note that if after load goes ldc instruction it could be checkParametersNotNull method invocation
|
||||
private fun hasOnlyInvokeStatic(m: Method): Boolean {
|
||||
val bytecodes = m.bytecodes()
|
||||
var i = 0
|
||||
var isALoad0BeforeStaticCall = false
|
||||
while (i < bytecodes.size) {
|
||||
val instr = bytecodes[i]
|
||||
when {
|
||||
instr == 42.toByte() /* ALOAD_0 */ -> {
|
||||
i += 1
|
||||
isALoad0BeforeStaticCall = true
|
||||
}
|
||||
instr in LOAD_INSTRUCTIONS_WITH_INDEX || instr in LOAD_INSTRUCTIONS -> {
|
||||
i += 1
|
||||
if (instr in LOAD_INSTRUCTIONS_WITH_INDEX) i += 1
|
||||
val nextInstr = bytecodes[i]
|
||||
if (nextInstr == Opcodes.LDC.toByte()) {
|
||||
i += 2
|
||||
isALoad0BeforeStaticCall = false
|
||||
}
|
||||
}
|
||||
instr == Opcodes.INVOKESTATIC.toByte() -> {
|
||||
i += 3
|
||||
if (isALoad0BeforeStaticCall && i == (bytecodes.size - 1)) {
|
||||
val nextInstr = bytecodes[i]
|
||||
return nextInstr in RETURN_INSTRUCTIONS
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: class DefaultImpl can be not loaded
|
||||
private fun hasInterfaceWithImplementation(method: Method): Boolean {
|
||||
val declaringType = method.declaringType() as? ClassType ?: return false
|
||||
val interfaces = declaringType.allInterfaces()
|
||||
val vm = declaringType.virtualMachine()
|
||||
val traitImpls = interfaces.flatMap { vm.classesByName(it.name() + JvmAbi.DEFAULT_IMPLS_SUFFIX) }
|
||||
return traitImpls.any { !it.methodsByName(method.name()).isEmpty() }
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.render
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiExpression
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
class DelegatedPropertyFieldDescriptor(
|
||||
project: Project,
|
||||
objectRef: ObjectReference,
|
||||
val delegate: Field,
|
||||
private val renderDelegatedProperty: Boolean
|
||||
) : FieldDescriptorImpl(project, objectRef, delegate) {
|
||||
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? {
|
||||
if (evaluationContext == null) return null
|
||||
if (!renderDelegatedProperty) return super.calcValue(evaluationContext)
|
||||
|
||||
val method = findGetterForDelegatedProperty()
|
||||
val threadReference = evaluationContext.suspendContext.thread?.threadReference
|
||||
if (method == null || threadReference == null) {
|
||||
return super.calcValue(evaluationContext)
|
||||
}
|
||||
|
||||
try {
|
||||
return evaluationContext.debugProcess.invokeInstanceMethod(
|
||||
evaluationContext,
|
||||
`object`,
|
||||
method,
|
||||
listOf<Nothing>(),
|
||||
evaluationContext.suspendContext.suspendPolicy
|
||||
)
|
||||
}
|
||||
catch(e: EvaluateException) {
|
||||
return e.exceptionFromTargetVM
|
||||
}
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return delegate.name().removeSuffix(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)
|
||||
}
|
||||
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findGetterForDelegatedProperty(): Method? {
|
||||
val fieldName = name
|
||||
if (!Name.isValidIdentifier(fieldName)) return null
|
||||
|
||||
return `object`.referenceType().methodsByName(JvmAbi.getterName(fieldName))?.firstOrNull()
|
||||
}
|
||||
|
||||
override fun getDeclaredType(): String? {
|
||||
val getter = findGetterForDelegatedProperty() ?: return null
|
||||
val returnType = try {
|
||||
getter.returnType()
|
||||
} catch (e: ClassNotLoadedException) {
|
||||
// Behavior copied from LocalVariableDescriptorImpl (in platform)
|
||||
return "<unknown>"
|
||||
}
|
||||
return returnType?.name()
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.render
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.settings.NodeRendererSettings
|
||||
import com.intellij.debugger.ui.impl.watch.MessageDescriptor
|
||||
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
|
||||
import com.intellij.debugger.ui.tree.DebuggerTreeNode
|
||||
import com.intellij.debugger.ui.tree.ValueDescriptor
|
||||
import com.intellij.debugger.ui.tree.render.ChildrenBuilder
|
||||
import com.intellij.debugger.ui.tree.render.ClassRenderer
|
||||
import com.intellij.debugger.ui.tree.render.DescriptorLabelListener
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettingsManager
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.Type
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
import org.jetbrains.kotlin.idea.debugger.ToggleKotlinVariablesState
|
||||
import org.jetbrains.kotlin.idea.debugger.canRunEvaluation
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import java.util.*
|
||||
import com.sun.jdi.Type as JdiType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
private val LOG = Logger.getInstance(KotlinClassWithDelegatedPropertyRenderer::class.java)
|
||||
private fun notPreparedClassMessage(referenceType: ReferenceType) =
|
||||
"$referenceType ${referenceType.isPrepared} ${referenceType.sourceName()}"
|
||||
|
||||
class KotlinClassWithDelegatedPropertyRenderer : ClassRenderer() {
|
||||
private val rendererSettings = NodeRendererSettings.getInstance()
|
||||
|
||||
override fun isApplicable(jdiType: Type?): Boolean {
|
||||
if (!super.isApplicable(jdiType)) return false
|
||||
|
||||
if (jdiType !is ReferenceType) return false
|
||||
|
||||
if (!jdiType.isPrepared) {
|
||||
LOG.info(notPreparedClassMessage(jdiType))
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
return jdiType.allFields().any { it.name().endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) }
|
||||
} catch (notPrepared: ClassNotPreparedException) {
|
||||
LOG.error(notPreparedClassMessage(jdiType), notPrepared)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun calcLabel(
|
||||
descriptor: ValueDescriptor,
|
||||
evaluationContext: EvaluationContext,
|
||||
listener: DescriptorLabelListener
|
||||
): String {
|
||||
val res = calcToStringLabel(descriptor, evaluationContext, listener)
|
||||
if (res != null) {
|
||||
return res
|
||||
}
|
||||
|
||||
return super.calcLabel(descriptor, evaluationContext, listener)
|
||||
}
|
||||
|
||||
private fun calcToStringLabel(
|
||||
descriptor: ValueDescriptor, evaluationContext: EvaluationContext,
|
||||
listener: DescriptorLabelListener
|
||||
): String? {
|
||||
val toStringRenderer = rendererSettings.toStringRenderer
|
||||
if (toStringRenderer.isEnabled && DebuggerManagerEx.getInstanceEx(evaluationContext.project).context.canRunEvaluation) {
|
||||
if (toStringRenderer.isApplicable(descriptor.type)) {
|
||||
return toStringRenderer.calcLabel(descriptor, evaluationContext, listener)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun buildChildren(value: Value?, builder: ChildrenBuilder, context: EvaluationContext) {
|
||||
DebuggerManagerThreadImpl.assertIsManagerThread()
|
||||
|
||||
if (value !is ObjectReference) return
|
||||
|
||||
val nodeManager = builder.nodeManager!!
|
||||
val nodeDescriptorFactory = builder.descriptorManager!!
|
||||
|
||||
val fields = value.referenceType().allFields()
|
||||
if (fields.isEmpty()) {
|
||||
builder.setChildren(listOf(nodeManager.createMessageNode(MessageDescriptor.CLASS_HAS_NO_FIELDS.label)))
|
||||
return
|
||||
}
|
||||
|
||||
val children = ArrayList<DebuggerTreeNode>()
|
||||
for (field in fields) {
|
||||
if (!shouldDisplay(context, value, field)) {
|
||||
continue
|
||||
}
|
||||
|
||||
val fieldDescriptor = nodeDescriptorFactory.getFieldDescriptor(builder.parentDescriptor, value, field)
|
||||
|
||||
if (field.name().endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)) {
|
||||
val shouldRenderDelegatedProperty = KotlinDebuggerSettings.getInstance().DEBUG_RENDER_DELEGATED_PROPERTIES
|
||||
if (shouldRenderDelegatedProperty && !ToggleKotlinVariablesState.getService().kotlinVariableView) {
|
||||
children.add(nodeManager.createNode(fieldDescriptor, context))
|
||||
}
|
||||
|
||||
val delegatedPropertyDescriptor = DelegatedPropertyFieldDescriptor(
|
||||
context.debugProcess.project!!,
|
||||
value,
|
||||
field,
|
||||
shouldRenderDelegatedProperty
|
||||
)
|
||||
children.add(nodeManager.createNode(delegatedPropertyDescriptor, context))
|
||||
} else {
|
||||
children.add(nodeManager.createNode(fieldDescriptor, context))
|
||||
}
|
||||
}
|
||||
|
||||
if (XDebuggerSettingsManager.getInstance()!!.dataViewSettings.isSortValues) {
|
||||
children.sortedWith(NodeManagerImpl.getNodeComparator())
|
||||
}
|
||||
|
||||
builder.setChildren(children)
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping;
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||
import com.intellij.debugger.engine.RequestHint;
|
||||
import com.intellij.debugger.engine.SuspendContextImpl;
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
|
||||
import com.sun.jdi.request.StepRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DebugProcessImplHelper {
|
||||
public static DebugProcessImpl.StepOverCommand createStepOverCommandWithCustomFilter(
|
||||
SuspendContextImpl suspendContext,
|
||||
boolean ignoreBreakpoints,
|
||||
KotlinSuspendCallStepOverFilter methodFilter
|
||||
) {
|
||||
DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||
return debugProcess.new StepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread) {
|
||||
@SuppressWarnings("MagicConstant")
|
||||
RequestHint hint = new RequestHintWithMethodFilter(stepThread, suspendContext, StepRequest.STEP_OVER, methodFilter);
|
||||
hint.setRestoreBreakpoints(ignoreBreakpoints);
|
||||
hint.setIgnoreFilters(ignoreBreakpoints || debugProcess.getSession().shouldIgnoreSteppingFilters());
|
||||
|
||||
return hint;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.stepping;
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||
import com.intellij.debugger.engine.SuspendContextImpl;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl;
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
|
||||
import com.intellij.debugger.settings.DebuggerSettings;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.ui.classFilter.ClassFilter;
|
||||
import com.intellij.ui.classFilter.DebuggerClassFilterProvider;
|
||||
import com.sun.jdi.Location;
|
||||
import com.sun.jdi.ObjectCollectedException;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import com.sun.jdi.ThreadReference;
|
||||
import com.sun.jdi.request.EventRequest;
|
||||
import com.sun.jdi.request.EventRequestManager;
|
||||
import com.sun.jdi.request.StepRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.debugger.NoStrataPositionManagerHelperKt;
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral;
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class DebuggerSteppingHelper {
|
||||
private static Logger LOG = Logger.getInstance(DebuggerSteppingHelper.class);
|
||||
|
||||
public static DebugProcessImpl.ResumeCommand createStepOverCommand(
|
||||
final SuspendContextImpl suspendContext,
|
||||
final boolean ignoreBreakpoints,
|
||||
final KotlinSteppingCommandProvider.KotlinSourcePosition kotlinSourcePosition
|
||||
) {
|
||||
final DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||
|
||||
return debugProcess.new ResumeCommand(suspendContext) {
|
||||
@Override
|
||||
public void contextAction() {
|
||||
boolean isDexDebug = NoStrataPositionManagerHelperKt.isDexDebug(suspendContext.getDebugProcess());
|
||||
|
||||
try {
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
if (frameProxy != null) {
|
||||
Action action = KotlinSteppingCommandProviderKt.getStepOverAction(
|
||||
frameProxy.location(),
|
||||
kotlinSourcePosition,
|
||||
frameProxy,
|
||||
isDexDebug
|
||||
);
|
||||
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
debugProcess.getVirtualMachineProxy().eventRequestManager(),
|
||||
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
|
||||
|
||||
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
debugProcess.createStepOutCommand(suspendContext).contextAction();
|
||||
}
|
||||
catch (EvaluateException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static DebugProcessImpl.ResumeCommand createStepOutCommand(
|
||||
final SuspendContextImpl suspendContext,
|
||||
final boolean ignoreBreakpoints,
|
||||
final List<KtNamedFunction> inlineFunctions,
|
||||
final KtFunctionLiteral inlineArgument
|
||||
) {
|
||||
final DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||
return debugProcess.new ResumeCommand(suspendContext) {
|
||||
@Override
|
||||
public void contextAction() {
|
||||
try {
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
if (frameProxy != null) {
|
||||
Action action = KotlinSteppingCommandProviderKt.getStepOutAction(
|
||||
frameProxy.location(),
|
||||
suspendContext,
|
||||
inlineFunctions,
|
||||
inlineArgument
|
||||
);
|
||||
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
debugProcess.getVirtualMachineProxy().eventRequestManager(),
|
||||
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
|
||||
|
||||
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
|
||||
}
|
||||
catch (EvaluateException ignored) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// copied from DebugProcessImpl.doStep
|
||||
private static void createStepRequest(
|
||||
@NotNull SuspendContextImpl suspendContext,
|
||||
@Nullable ThreadReferenceProxyImpl stepThread,
|
||||
@NotNull EventRequestManager requestManager,
|
||||
int size, int depth
|
||||
) {
|
||||
if (stepThread == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ThreadReference stepThreadReference = stepThread.getThreadReference();
|
||||
|
||||
requestManager.deleteEventRequests(requestManager.stepRequests());
|
||||
|
||||
StepRequest stepRequest = requestManager.createStepRequest(stepThreadReference, size, depth);
|
||||
|
||||
List<ClassFilter> activeFilters = getActiveFilters();
|
||||
|
||||
if (!activeFilters.isEmpty()) {
|
||||
String currentClassName = getCurrentClassName(stepThread);
|
||||
if (currentClassName == null || !DebuggerUtilsEx.isFiltered(currentClassName, activeFilters)) {
|
||||
// add class filters
|
||||
for (ClassFilter filter : activeFilters) {
|
||||
stepRequest.addClassExclusionFilter(filter.getPattern());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// suspend policy to match the suspend policy of the context:
|
||||
// if all threads were suspended, then during stepping all the threads must be suspended
|
||||
// if only event thread were suspended, then only this particular thread must be suspended during stepping
|
||||
stepRequest.setSuspendPolicy(suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD
|
||||
? EventRequest.SUSPEND_EVENT_THREAD
|
||||
: EventRequest.SUSPEND_ALL);
|
||||
|
||||
stepRequest.enable();
|
||||
}
|
||||
catch (ObjectCollectedException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// copied from DebugProcessImpl.getActiveFilters
|
||||
@NotNull
|
||||
private static List<ClassFilter> getActiveFilters() {
|
||||
List<ClassFilter> activeFilters = new ArrayList<ClassFilter>();
|
||||
DebuggerSettings settings = DebuggerSettings.getInstance();
|
||||
if (settings.TRACING_FILTERS_ENABLED) {
|
||||
for (ClassFilter filter : settings.getSteppingFilters()) {
|
||||
if (filter.isEnabled()) {
|
||||
activeFilters.add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (DebuggerClassFilterProvider provider : Extensions.getExtensions(DebuggerClassFilterProvider.EP_NAME)) {
|
||||
for (ClassFilter filter : provider.getFilters()) {
|
||||
if (filter.isEnabled()) {
|
||||
activeFilters.add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return activeFilters;
|
||||
}
|
||||
|
||||
// copied from DebugProcessImpl.getActiveFilters
|
||||
@Nullable
|
||||
private static String getCurrentClassName(ThreadReferenceProxyImpl thread) {
|
||||
try {
|
||||
if (thread != null && thread.frameCount() > 0) {
|
||||
StackFrameProxyImpl stackFrame = thread.frame(0);
|
||||
if (stackFrame != null) {
|
||||
Location location = stackFrame.location();
|
||||
ReferenceType referenceType = location == null ? null : location.declaringType();
|
||||
if (referenceType != null) {
|
||||
return referenceType.name();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EvaluateException ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+95
@@ -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.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.NamedMethodFilter
|
||||
import com.intellij.psi.SmartPsiElementPointer
|
||||
import com.intellij.util.Range
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DECLARATION
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
|
||||
class KotlinBasicStepMethodFilter(
|
||||
private val declarationPtr: SmartPsiElementPointer<KtDeclaration>?,
|
||||
private val isInvoke: Boolean,
|
||||
private val targetMethodName: String,
|
||||
private val myCallingExpressionLines: Range<Int>
|
||||
) : NamedMethodFilter {
|
||||
init {
|
||||
assert(declarationPtr != null || isInvoke)
|
||||
}
|
||||
|
||||
override fun getCallingExpressionLines() = myCallingExpressionLines
|
||||
|
||||
override fun getMethodName() = targetMethodName
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||
val method = location.method()
|
||||
if (targetMethodName != method.name()) return false
|
||||
|
||||
val positionManager = process.positionManager
|
||||
|
||||
val (currentDescriptor, currentDeclaration) = runReadAction {
|
||||
val elementAt = positionManager.getSourcePosition(location)?.elementAt
|
||||
|
||||
val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) {
|
||||
it !is KtProperty || !it.isLocal
|
||||
}
|
||||
|
||||
if (declaration is KtClass && method.name() == "<init>") {
|
||||
declaration.resolveToDescriptorIfAny()?.unsubstitutedPrimaryConstructor to declaration
|
||||
} else {
|
||||
declaration?.resolveToDescriptorIfAny() to declaration
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDescriptor == null || currentDeclaration == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (currentDescriptor !is CallableMemberDescriptor) return false
|
||||
if (currentDescriptor.kind != DECLARATION) return false
|
||||
|
||||
if (isInvoke) {
|
||||
// There can be only one 'invoke' target at the moment so consider position as expected.
|
||||
// Descriptors can be not-equal, say when parameter has type `(T) -> T` and lambda is `Int.() -> Int`.
|
||||
return true
|
||||
}
|
||||
|
||||
val declaration = declarationPtr?.element
|
||||
?: return true // Element is lost. But we know that name is matches, so stop.
|
||||
|
||||
if (currentDeclaration.isEquivalentTo(declaration)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return DescriptorUtils.getAllOverriddenDescriptors(currentDescriptor).any { baseOfCurrent ->
|
||||
val currentBaseDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(currentDeclaration.project, baseOfCurrent)
|
||||
declaration.isEquivalentTo(currentBaseDeclaration)
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.BreakpointStepMethodFilter
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.util.Range
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.codegen.coroutines.isResumeImplMethodNameFromAnyLanguageSettings
|
||||
import org.jetbrains.kotlin.idea.core.util.isMultiLine
|
||||
import org.jetbrains.kotlin.idea.debugger.isInsideInlineArgument
|
||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
class KotlinLambdaMethodFilter(
|
||||
private val lambda: KtFunction,
|
||||
private val myCallingExpressionLines: Range<Int>,
|
||||
private val isInline: Boolean,
|
||||
private val isSuspend: Boolean
|
||||
) : BreakpointStepMethodFilter {
|
||||
private val myFirstStatementPosition: SourcePosition?
|
||||
private val myLastStatementLine: Int
|
||||
|
||||
init {
|
||||
val body = lambda.bodyExpression
|
||||
if (body != null && lambda.isMultiLine()) {
|
||||
var firstStatementPosition: SourcePosition? = null
|
||||
var lastStatementPosition: SourcePosition? = null
|
||||
val statements = (body as? KtBlockExpression)?.statements ?: listOf(body)
|
||||
if (statements.isNotEmpty()) {
|
||||
firstStatementPosition = SourcePosition.createFromElement(statements.first())
|
||||
if (firstStatementPosition != null) {
|
||||
val lastStatement = statements.last()
|
||||
lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.file, lastStatement.textRange.endOffset)
|
||||
}
|
||||
}
|
||||
myFirstStatementPosition = firstStatementPosition
|
||||
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.line else -1
|
||||
} else {
|
||||
myFirstStatementPosition = SourcePosition.createFromElement(lambda)
|
||||
myLastStatementLine = myFirstStatementPosition!!.line
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBreakpointPosition() = myFirstStatementPosition
|
||||
override fun getLastStatementLine() = myLastStatementLine
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||
val method = location.method()
|
||||
|
||||
if (isInline) {
|
||||
return isInsideInlineArgument(lambda, location, process)
|
||||
}
|
||||
|
||||
return isLambdaName(method.name())
|
||||
}
|
||||
|
||||
override fun getCallingExpressionLines() = if (isInline) Range(0, Int.MAX_VALUE) else myCallingExpressionLines
|
||||
|
||||
private fun isLambdaName(name: String?): Boolean {
|
||||
if (isSuspend && name != null) {
|
||||
return isResumeImplMethodNameFromAnyLanguageSettings(name)
|
||||
}
|
||||
|
||||
return name == OperatorNameConventions.INVOKE.asString()
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.actions.SmartStepTarget
|
||||
import com.intellij.util.Range
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinLambdaSmartStepTarget(
|
||||
label: String,
|
||||
highlightElement: KtFunction,
|
||||
lines: Range<Int>,
|
||||
val isInline: Boolean,
|
||||
val isSuspend: Boolean
|
||||
) : SmartStepTarget(label, highlightElement, true, lines) {
|
||||
override fun getIcon(): Icon = KotlinIcons.LAMBDA
|
||||
|
||||
fun getLambda() = highlightElement as KtFunction
|
||||
|
||||
companion object {
|
||||
fun calcLabel(descriptor: DeclarationDescriptor, paramName: Name): String {
|
||||
return "${descriptor.name.asString()}: ${paramName.asString()}.${OperatorNameConventions.INVOKE.asString()}()"
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.actions.SmartStepTarget
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.Range
|
||||
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
|
||||
import org.jetbrains.kotlin.renderer.PropertyAccessorRenderingPolicy
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinMethodSmartStepTarget(
|
||||
descriptor: CallableMemberDescriptor,
|
||||
val declaration: KtDeclaration?,
|
||||
label: String,
|
||||
highlightElement: PsiElement,
|
||||
lines: Range<Int>
|
||||
) : SmartStepTarget(label, highlightElement, false, lines) {
|
||||
val isInvoke = descriptor is FunctionInvokeDescriptor
|
||||
|
||||
init {
|
||||
assert(declaration != null || isInvoke)
|
||||
}
|
||||
|
||||
private val isExtension = descriptor.isExtension
|
||||
|
||||
val targetMethodName: String = when (descriptor) {
|
||||
is ClassDescriptor, is ConstructorDescriptor -> "<init>"
|
||||
is PropertyAccessorDescriptor -> JvmAbi.getterName(descriptor.correspondingProperty.name.asString())
|
||||
else -> descriptor.name.asString()
|
||||
}
|
||||
|
||||
override fun getIcon(): Icon? {
|
||||
return when {
|
||||
isExtension -> KotlinIcons.EXTENSION_FUNCTION
|
||||
else -> KotlinIcons.FUNCTION
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val renderer = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.withOptions {
|
||||
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
|
||||
withoutReturnType = true
|
||||
propertyAccessorRenderingPolicy = PropertyAccessorRenderingPolicy.PRETTY
|
||||
startFromName = true
|
||||
modifiers = emptySet()
|
||||
}
|
||||
|
||||
fun calcLabel(descriptor: DeclarationDescriptor): String {
|
||||
return renderer.render(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
|
||||
if (other == null || other !is KotlinMethodSmartStepTarget) return false
|
||||
|
||||
if (isInvoke && other.isInvoke) {
|
||||
// Don't allow to choose several invoke targets in smart step into as we can't distinguish them reliably during debug
|
||||
return true
|
||||
}
|
||||
|
||||
return declaration === other.declaration
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
if (isInvoke) {
|
||||
// Predefined value to make all FunctionInvokeDescriptor targets equal
|
||||
return 42
|
||||
}
|
||||
return declaration!!.hashCode()
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.SimplePropertyGetterProvider
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinSimpleGetterProvider : SimplePropertyGetterProvider {
|
||||
override fun isInsideSimpleGetter(element: PsiElement): Boolean {
|
||||
// class A(val a: Int)
|
||||
if (element is KtParameter) {
|
||||
return true
|
||||
}
|
||||
|
||||
val accessor = PsiTreeUtil.getParentOfType(element, KtPropertyAccessor::class.java)
|
||||
if (accessor != null && accessor.isGetter) {
|
||||
val body = accessor.bodyExpression
|
||||
return when (body) {
|
||||
is KtBlockExpression -> {
|
||||
// val a: Int get() { return field }
|
||||
val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false
|
||||
returnedExpression.textMatches("field")
|
||||
}
|
||||
is KtExpression -> body.textMatches("field") // val a: Int get() = field
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
val property = PsiTreeUtil.getParentOfType(element, KtProperty::class.java)
|
||||
// val a = foo()
|
||||
if (property != null) {
|
||||
return property.getter == null && !property.isLocal
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.actions.JvmSmartStepIntoHandler
|
||||
import com.intellij.debugger.actions.MethodSmartStepTarget
|
||||
import com.intellij.debugger.actions.SmartStepTarget
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.util.Range
|
||||
import com.intellij.util.containers.OrderedSet
|
||||
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
|
||||
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType
|
||||
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.isFromJava
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
|
||||
class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
|
||||
|
||||
override fun isAvailable(position: SourcePosition?) = position?.file is KtFile
|
||||
|
||||
override fun findSmartStepTargets(position: SourcePosition): List<SmartStepTarget> {
|
||||
val file = position.file
|
||||
|
||||
val elementAtOffset = position.elementAt ?: return emptyList()
|
||||
|
||||
val element = CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, elementAtOffset.textRange.startOffset) as? KtElement
|
||||
?: return emptyList()
|
||||
|
||||
val elementTextRange = element.textRange ?: return emptyList()
|
||||
|
||||
val doc = PsiDocumentManager.getInstance(file.project).getDocument(file) ?: return emptyList()
|
||||
|
||||
val lines = Range(doc.getLineNumber(elementTextRange.startOffset), doc.getLineNumber(elementTextRange.endOffset))
|
||||
@Suppress("DEPRECATION")
|
||||
val bindingContext = element.analyzeWithAllCompilerChecks().bindingContext
|
||||
val result = OrderedSet<SmartStepTarget>()
|
||||
|
||||
// TODO support class initializers, local functions, delegated properties with specified type, setter for properties
|
||||
element.accept(object : KtTreeVisitorVoid() {
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
recordFunctionLiteral(lambdaExpression.functionLiteral)
|
||||
}
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
if (!recordFunctionLiteral(function)) {
|
||||
super.visitNamedFunction(function)
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordFunctionLiteral(function: KtFunction): Boolean {
|
||||
val context = function.analyze()
|
||||
val resolvedCall = function.getParentCall(context).getResolvedCall(context)
|
||||
if (resolvedCall != null) {
|
||||
val arguments = resolvedCall.valueArguments
|
||||
for ((param, argument) in arguments) {
|
||||
if (argument.arguments.any { getArgumentExpression(it) == function }) {
|
||||
val resultingDescriptor = resolvedCall.resultingDescriptor
|
||||
val label = KotlinLambdaSmartStepTarget.calcLabel(resultingDescriptor, param.name)
|
||||
result.add(
|
||||
KotlinLambdaSmartStepTarget(
|
||||
label, function, lines, InlineUtil.isInline(resultingDescriptor), param.type.isSuspendFunctionType
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) =
|
||||
(it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
|
||||
// skip calls in object declarations
|
||||
}
|
||||
|
||||
override fun visitIfExpression(expression: KtIfExpression) {
|
||||
expression.condition?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitWhileExpression(expression: KtWhileExpression) {
|
||||
expression.condition?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitDoWhileExpression(expression: KtDoWhileExpression) {
|
||||
expression.condition?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitForExpression(expression: KtForExpression) {
|
||||
expression.loopRange?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitWhenExpression(expression: KtWhenExpression) {
|
||||
expression.subjectExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitArrayAccessExpression(expression: KtArrayAccessExpression) {
|
||||
recordFunction(expression)
|
||||
super.visitArrayAccessExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitUnaryExpression(expression: KtUnaryExpression) {
|
||||
recordFunction(expression.operationReference)
|
||||
super.visitUnaryExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitBinaryExpression(expression: KtBinaryExpression) {
|
||||
recordFunction(expression.operationReference)
|
||||
super.visitBinaryExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
val calleeExpression = expression.calleeExpression
|
||||
if (calleeExpression != null) {
|
||||
recordFunction(calleeExpression)
|
||||
}
|
||||
super.visitCallExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||
recordGetter(expression)
|
||||
super.visitSimpleNameExpression(expression)
|
||||
}
|
||||
|
||||
private fun recordGetter(expression: KtSimpleNameExpression) {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
|
||||
val propertyDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return
|
||||
|
||||
val getterDescriptor = propertyDescriptor.getter
|
||||
if (getterDescriptor == null || getterDescriptor.isDefault) return
|
||||
|
||||
val ktDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, getterDescriptor) as? KtDeclaration ?: return
|
||||
|
||||
val delegatedResolvedCall = bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor]
|
||||
if (delegatedResolvedCall != null) {
|
||||
val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor
|
||||
val label = "${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor)
|
||||
result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, ktDeclaration, label, expression, lines))
|
||||
} else {
|
||||
if (ktDeclaration is KtPropertyAccessor && ktDeclaration.hasBody()) {
|
||||
val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor)
|
||||
result.add(KotlinMethodSmartStepTarget(getterDescriptor, ktDeclaration, label, expression, lines))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordFunction(expression: KtExpression) {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
|
||||
|
||||
val descriptor = resolvedCall.resultingDescriptor
|
||||
if (descriptor !is FunctionDescriptor || isIntrinsic(descriptor)) return
|
||||
|
||||
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor)
|
||||
if (descriptor.isFromJava) {
|
||||
(declaration as? PsiMethod)?.let {
|
||||
result.add(MethodSmartStepTarget(it, null, declaration, false, lines))
|
||||
}
|
||||
} else {
|
||||
if (declaration == null && !isInvokeInBuiltinFunction(descriptor)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (declaration !is KtDeclaration?) return
|
||||
|
||||
if (descriptor is ConstructorDescriptor && descriptor.isPrimary) {
|
||||
if (declaration is KtClass && declaration.getAnonymousInitializers().isEmpty()) {
|
||||
// There is no constructor or init block, so do not show it in smart step into
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val callLabel = KotlinMethodSmartStepTarget.calcLabel(descriptor)
|
||||
val label = when (descriptor) {
|
||||
is FunctionInvokeDescriptor -> {
|
||||
when (expression) {
|
||||
is KtSimpleNameExpression -> "${runReadAction { expression.text }}.$callLabel"
|
||||
else -> callLabel
|
||||
}
|
||||
}
|
||||
else -> callLabel
|
||||
}
|
||||
|
||||
result.add(KotlinMethodSmartStepTarget(descriptor, declaration, label, expression, lines))
|
||||
}
|
||||
}
|
||||
}, null)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
|
||||
return when (stepTarget) {
|
||||
is KotlinMethodSmartStepTarget ->
|
||||
KotlinBasicStepMethodFilter(
|
||||
stepTarget.declaration?.createSmartPointer(),
|
||||
stepTarget.isInvoke,
|
||||
stepTarget.targetMethodName,
|
||||
stepTarget.callingExpressionLines!!
|
||||
)
|
||||
is KotlinLambdaSmartStepTarget ->
|
||||
KotlinLambdaMethodFilter(
|
||||
stepTarget.getLambda(), stepTarget.callingExpressionLines!!, stepTarget.isInline, stepTarget.isSuspend
|
||||
)
|
||||
else -> super.createMethodFilter(stepTarget)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val methods = IntrinsicMethods(JvmTarget.JVM_1_6)
|
||||
|
||||
private fun isIntrinsic(descriptor: CallableMemberDescriptor): Boolean {
|
||||
return methods.getIntrinsic(descriptor) != null
|
||||
}
|
||||
|
||||
private fun isInvokeInBuiltinFunction(descriptor: DeclarationDescriptor): Boolean {
|
||||
if (descriptor !is FunctionInvokeDescriptor) return false
|
||||
val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return false
|
||||
return classDescriptor.defaultType.isBuiltinFunctionalType
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.*
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerSession
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.EventDispatcher
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import com.sun.jdi.request.StepRequest
|
||||
import java.lang.reflect.Field
|
||||
|
||||
// Mass-copy-paste code for commands behaviour from com.intellij.debugger.engine.DebugProcessImpl
|
||||
@SuppressWarnings("UnnecessaryFinalOnLocalVariableOrParameter")
|
||||
class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
|
||||
abstract class KotlinStepAction {
|
||||
abstract fun contextAction(suspendContext: SuspendContextImpl)
|
||||
}
|
||||
|
||||
fun createKotlinStepOverInlineAction(smartStepFilter: KotlinMethodFilter): KotlinStepAction {
|
||||
return StepOverInlineCommand(smartStepFilter, StepRequest.STEP_LINE)
|
||||
}
|
||||
|
||||
private val debuggerContext: DebuggerContextImpl get() = debuggerProcess.debuggerContext
|
||||
private val suspendManager: SuspendManager get() = debuggerProcess.suspendManager
|
||||
private val project: Project get() = debuggerProcess.project
|
||||
private val session: DebuggerSession get() = debuggerProcess.session
|
||||
|
||||
// TODO: ask for better API
|
||||
// Should be safe to use reflection as field is protected and not obfuscated
|
||||
private val debugProcessDispatcher: EventDispatcher<DebugProcessListener> = getFromField("myDebugProcessDispatcher")
|
||||
|
||||
// TODO: ask for better API
|
||||
// Get field by type as it private and obfuscated in Ultimate
|
||||
private val threadBlockedMonitor: ThreadBlockedMonitor = getFromField(ThreadBlockedMonitor::class.java)
|
||||
|
||||
private fun showStatusText(message: String) {
|
||||
debuggerProcess.showStatusText(message)
|
||||
}
|
||||
|
||||
// TODO: ask for better API
|
||||
// Should be safe to use reflection as method is protected and not obfuscated
|
||||
private fun doStep(
|
||||
suspendContext: SuspendContextImpl,
|
||||
stepThread: ThreadReferenceProxyImpl,
|
||||
size: Int, depth: Int, hint: RequestHint
|
||||
) {
|
||||
val doStepMethod = DebugProcessImpl::class.java.getDeclaredMethod(
|
||||
"doStep",
|
||||
SuspendContextImpl::class.java, ThreadReferenceProxyImpl::class.java,
|
||||
Integer.TYPE, Integer.TYPE, RequestHint::class.java
|
||||
)
|
||||
|
||||
doStepMethod.isAccessible = true
|
||||
|
||||
doStepMethod.invoke(debuggerProcess, suspendContext, stepThread, size, depth, hint)
|
||||
}
|
||||
|
||||
private fun <T> getFromField(fieldType: Class<T>): T {
|
||||
return getFromField(DebugProcessImpl::class.java.declaredFields.single { it.type == fieldType })
|
||||
}
|
||||
|
||||
private fun <T> getFromField(fieldName: String): T {
|
||||
return getFromField(DebugProcessImpl::class.java.getDeclaredField(fieldName))
|
||||
}
|
||||
|
||||
private fun <T> getFromField(field: Field?): T {
|
||||
field!!.isAccessible = true
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return field.get(debuggerProcess) as T
|
||||
}
|
||||
|
||||
private inner class StepOverInlineCommand(private val mySmartStepFilter: KotlinMethodFilter, private val myStepSize: Int) :
|
||||
KotlinStepAction() {
|
||||
private fun getContextThread(suspendContext: SuspendContextImpl): ThreadReferenceProxyImpl? {
|
||||
val contextThread = debuggerContext.threadProxy
|
||||
return contextThread ?: suspendContext.thread
|
||||
}
|
||||
|
||||
// See: ResumeCommand.applyThreadFilter()
|
||||
private fun applyThreadFilter(suspendContext: SuspendContextImpl, thread: ThreadReferenceProxyImpl) {
|
||||
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||
// there could be explicit resume as a result of call to voteSuspend()
|
||||
// e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_
|
||||
// resuming and all breakpoints in other threads will be ignored.
|
||||
// As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens
|
||||
val breakpointManager = DebuggerManagerEx.getInstanceEx(project).breakpointManager
|
||||
breakpointManager.applyThreadFilter(debuggerProcess, thread.threadReference)
|
||||
}
|
||||
}
|
||||
|
||||
// See: StepCommand.resumeAction()
|
||||
private fun resumeAction(suspendContext: SuspendContextImpl, thread: ThreadReferenceProxyImpl) {
|
||||
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD || isResumeOnlyCurrentThread) {
|
||||
threadBlockedMonitor.startWatching(thread)
|
||||
}
|
||||
if (isResumeOnlyCurrentThread && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||
suspendManager.resumeThread(suspendContext, thread)
|
||||
} else {
|
||||
suspendManager.resume(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
// See: StepIntoCommand.contextAction()
|
||||
override fun contextAction(suspendContext: SuspendContextImpl) {
|
||||
showStatusText("Stepping over inline")
|
||||
val stepThread = getContextThread(suspendContext)
|
||||
|
||||
if (stepThread == null) {
|
||||
// TODO: Intellij code doesn't bother to check thread for null, so probably it's not-null actually
|
||||
debuggerProcess.createStepOverCommand(suspendContext, true).contextAction(suspendContext)
|
||||
return
|
||||
}
|
||||
|
||||
val hint = KotlinStepOverInlinedLinesHint(stepThread, suspendContext, mySmartStepFilter)
|
||||
hint.isResetIgnoreFilters = !session.shouldIgnoreSteppingFilters()
|
||||
|
||||
try {
|
||||
session.setIgnoreStepFiltersFlag(stepThread.frameCount())
|
||||
} catch (e: EvaluateException) {
|
||||
LOG.info(e)
|
||||
}
|
||||
|
||||
applyThreadFilter(suspendContext, stepThread)
|
||||
|
||||
doStep(suspendContext, stepThread, myStepSize, StepRequest.STEP_OVER, hint)
|
||||
|
||||
showStatusText("Process resumed")
|
||||
resumeAction(suspendContext, stepThread)
|
||||
debugProcessDispatcher.multicaster.resumed(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(KotlinStepActionFactory::class.java)
|
||||
|
||||
private val isResumeOnlyCurrentThread: Boolean
|
||||
get() = DebuggerSettings.getInstance().RESUME_ONLY_CURRENT_THREAD
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.Range
|
||||
import com.sun.jdi.LocalVariable
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.idea.debugger.ktLocationInfo
|
||||
|
||||
class StepOverFilterData(
|
||||
val lineNumber: Int,
|
||||
val stepOverLines: Set<Int>,
|
||||
val inlineRangeVariables: List<LocalVariable>,
|
||||
val isDexDebug: Boolean,
|
||||
val skipAfterCodeIndex: Long = -1
|
||||
)
|
||||
|
||||
class KotlinStepOverInlineFilter(val project: Project, val data: StepOverFilterData) : KotlinMethodFilter {
|
||||
private fun Location.ktLineNumber() = ktLocationInfo(this, data.isDexDebug, project).first
|
||||
|
||||
override fun locationMatches(context: SuspendContextImpl, location: Location): Boolean {
|
||||
val frameProxy = context.frameProxy ?: return true
|
||||
|
||||
if (data.skipAfterCodeIndex != -1L && location.codeIndex() > data.skipAfterCodeIndex) {
|
||||
return false
|
||||
}
|
||||
|
||||
val currentLine = location.ktLineNumber()
|
||||
if (!(data.stepOverLines.contains(currentLine))) {
|
||||
return currentLine != data.lineNumber
|
||||
}
|
||||
|
||||
val visibleInlineVariables = getInlineRangeLocalVariables(frameProxy)
|
||||
|
||||
// Our ranges check missed exit from inline function. This is when breakpoint was in last statement of inline functions.
|
||||
// This can be observed by inline local range-variables. Absence of any means step out was done.
|
||||
return data.inlineRangeVariables.any { !visibleInlineVariables.contains(it) }
|
||||
}
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||
throw IllegalStateException() // Should not be called from Kotlin hint
|
||||
}
|
||||
|
||||
override fun getCallingExpressionLines(): Range<Int>? = null
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.RequestHint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.sun.jdi.VMDisconnectedException
|
||||
import com.sun.jdi.request.StepRequest
|
||||
|
||||
// Originally copied from RequestHint
|
||||
class KotlinStepOverInlinedLinesHint(
|
||||
stepThread: ThreadReferenceProxyImpl,
|
||||
suspendContext: SuspendContextImpl,
|
||||
methodFilter: KotlinMethodFilter
|
||||
) : RequestHint(stepThread, suspendContext, methodFilter) {
|
||||
|
||||
private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java)
|
||||
|
||||
private val filter = methodFilter
|
||||
|
||||
override fun getDepth(): Int = StepRequest.STEP_OVER
|
||||
|
||||
override fun getNextStepDepth(context: SuspendContextImpl): Int {
|
||||
try {
|
||||
val frameProxy = context.frameProxy
|
||||
if (frameProxy != null) {
|
||||
if (isTheSameFrame(context)) {
|
||||
return if (filter.locationMatches(context, frameProxy.location())) {
|
||||
STOP
|
||||
} else {
|
||||
StepRequest.STEP_OVER
|
||||
}
|
||||
}
|
||||
|
||||
if (isSteppedOut) {
|
||||
return STOP
|
||||
}
|
||||
|
||||
return StepRequest.STEP_OUT
|
||||
}
|
||||
} catch (ignored: VMDisconnectedException) {
|
||||
} catch (e: EvaluateException) {
|
||||
LOG.error(e)
|
||||
}
|
||||
|
||||
return STOP
|
||||
}
|
||||
}
|
||||
+599
@@ -0,0 +1,599 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.NoDataException
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.JvmSteppingCommandProvider
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.LocalVariable
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.Method
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.GOTO
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.MOVE
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_OBJECT
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_VOID
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_WIDE
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
|
||||
override fun getStepOverCommand(
|
||||
suspendContext: SuspendContextImpl?,
|
||||
ignoreBreakpoints: Boolean,
|
||||
stepSize: Int
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
if (suspendContext == null || suspendContext.isResumed) return null
|
||||
|
||||
val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null
|
||||
return getStepOverCommand(suspendContext, ignoreBreakpoints, sourcePosition)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun getStepOverCommand(
|
||||
suspendContext: SuspendContextImpl,
|
||||
ignoreBreakpoints: Boolean,
|
||||
debuggerContext: DebuggerContextImpl
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
return getStepOverCommand(suspendContext, ignoreBreakpoints, debuggerContext.sourcePosition)
|
||||
}
|
||||
|
||||
private fun getStepOverCommand(
|
||||
suspendContext: SuspendContextImpl,
|
||||
ignoreBreakpoints: Boolean,
|
||||
sourcePosition: SourcePosition
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
val kotlinSourcePosition = KotlinSourcePosition.create(sourcePosition) ?: return null
|
||||
|
||||
if (isSpecialStepOverNeeded(kotlinSourcePosition)) {
|
||||
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, kotlinSourcePosition)
|
||||
}
|
||||
|
||||
val file = sourcePosition.elementAt.containingFile
|
||||
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.safeLocation() } ?: return null
|
||||
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
|
||||
return DebugProcessImplHelper.createStepOverCommandWithCustomFilter(
|
||||
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file, ignoreBreakpoints)
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
data class KotlinSourcePosition(
|
||||
val file: KtFile, val function: KtNamedFunction,
|
||||
val linesRange: IntRange, val sourcePosition: SourcePosition
|
||||
) {
|
||||
companion object {
|
||||
fun create(sourcePosition: SourcePosition): KotlinSourcePosition? {
|
||||
val file = sourcePosition.file as? KtFile ?: return null
|
||||
if (sourcePosition.line < 0) return null
|
||||
|
||||
val elementAt = sourcePosition.elementAt ?: return null
|
||||
val containingFunction = elementAt.parents
|
||||
.filterIsInstance<KtNamedFunction>()
|
||||
.firstOrNull { !it.isLocal } ?: return null
|
||||
|
||||
val startLineNumber = containingFunction.getLineNumber(true) + 1
|
||||
val endLineNumber = containingFunction.getLineNumber(false) + 1
|
||||
if (startLineNumber > endLineNumber) return null
|
||||
|
||||
val linesRange = startLineNumber..endLineNumber
|
||||
|
||||
return KotlinSourcePosition(file, containingFunction, linesRange, sourcePosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSpecialStepOverNeeded(kotlinSourcePosition: KotlinSourcePosition): Boolean {
|
||||
val sourcePosition = kotlinSourcePosition.sourcePosition
|
||||
|
||||
val hasInlineCallsOnLine = getInlineFunctionCallsIfAny(sourcePosition).isNotEmpty()
|
||||
if (hasInlineCallsOnLine) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Step over calls to lambda arguments in inline function while execution is already in that function
|
||||
val containingFunctionDescriptor = kotlinSourcePosition.function.unsafeResolveToDescriptor()
|
||||
if (InlineUtil.isInline(containingFunctionDescriptor)) {
|
||||
val inlineArgumentsCallsIfAny = getInlineArgumentsCallsIfAny(sourcePosition, containingFunctionDescriptor)
|
||||
if (inlineArgumentsCallsIfAny != null && inlineArgumentsCallsIfAny.isNotEmpty()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun getStepOutCommand(suspendContext: SuspendContextImpl, debugContext: DebuggerContextImpl): DebugProcessImpl.ResumeCommand? {
|
||||
return getStepOutCommand(suspendContext, debugContext.sourcePosition)
|
||||
}
|
||||
|
||||
override fun getStepOutCommand(suspendContext: SuspendContextImpl?, stepSize: Int): DebugProcessImpl.ResumeCommand? {
|
||||
if (suspendContext == null || suspendContext.isResumed) return null
|
||||
|
||||
val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null
|
||||
return getStepOutCommand(suspendContext, sourcePosition)
|
||||
}
|
||||
|
||||
private fun getStepOutCommand(suspendContext: SuspendContextImpl, sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? {
|
||||
val file = sourcePosition.file as? KtFile ?: return null
|
||||
if (sourcePosition.line < 0) return null
|
||||
|
||||
val lineStartOffset = file.getLineStartOffset(sourcePosition.line) ?: return null
|
||||
|
||||
val inlineFunctions = getInlineFunctionsIfAny(file, lineStartOffset)
|
||||
val inlinedArgument = getInlineArgumentIfAny(sourcePosition.elementAt)
|
||||
|
||||
if (inlineFunctions.isEmpty() && inlinedArgument == null) return null
|
||||
|
||||
return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiElement?.contains(element: PsiElement): Boolean {
|
||||
return this?.textRange?.contains(element.textRange) ?: false
|
||||
}
|
||||
|
||||
private fun getInlineCallFunctionArgumentsIfAny(sourcePosition: SourcePosition): List<KtFunction> {
|
||||
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
|
||||
return getInlineArgumentsIfAny(inlineFunctionCalls)
|
||||
}
|
||||
|
||||
private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunction> {
|
||||
val elementAt = file.findElementAt(offset) ?: return emptyList()
|
||||
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
|
||||
|
||||
val descriptor = containingFunction.unsafeResolveToDescriptor()
|
||||
if (!InlineUtil.isInline(descriptor)) return emptyList()
|
||||
|
||||
return DebuggerUtils.analyzeElementWithInline(containingFunction, false).filterIsInstance<KtNamedFunction>()
|
||||
}
|
||||
|
||||
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
|
||||
return inlineFunctionCalls.flatMap {
|
||||
it.valueArguments
|
||||
.map(::getArgumentExpression)
|
||||
.filterIsInstance<KtFunction>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) =
|
||||
(it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
private fun getInlineArgumentsCallsIfAny(
|
||||
sourcePosition: SourcePosition,
|
||||
declarationDescriptor: DeclarationDescriptor
|
||||
): List<KtCallExpression>? {
|
||||
if (declarationDescriptor !is CallableDescriptor) return null
|
||||
|
||||
val valueParameters = declarationDescriptor.valueParameters.filter { it.type.isFunctionType }.toSet()
|
||||
|
||||
if (valueParameters.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
fun isCallOfArgument(ktCallExpression: KtCallExpression): Boolean {
|
||||
val resolvedCall = ktCallExpression.resolveToCall() as? VariableAsFunctionResolvedCall ?: return false
|
||||
|
||||
val candidateDescriptor = resolvedCall.variableCall.candidateDescriptor
|
||||
|
||||
return candidateDescriptor in valueParameters
|
||||
}
|
||||
|
||||
return findCallsOnPosition(sourcePosition, ::isCallOfArgument)
|
||||
}
|
||||
|
||||
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
|
||||
fun isInlineCall(expr: KtCallExpression): Boolean {
|
||||
val resolvedCall = expr.resolveToCall() ?: return false
|
||||
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
|
||||
}
|
||||
|
||||
return findCallsOnPosition(sourcePosition, ::isInlineCall)
|
||||
}
|
||||
|
||||
private fun findCallsOnPosition(sourcePosition: SourcePosition, filter: (KtCallExpression) -> Boolean): List<KtCallExpression> {
|
||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||
val lineNumber = sourcePosition.line
|
||||
|
||||
val lineElement = findElementAtLine(file, lineNumber)
|
||||
|
||||
if (lineElement !is KtElement) {
|
||||
if (lineElement != null) {
|
||||
val call = findCallByEndToken(lineElement)
|
||||
if (call != null && filter(call)) {
|
||||
return listOf(call)
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val start = lineElement.startOffset
|
||||
val end = lineElement.endOffset
|
||||
|
||||
val allFilteredCalls = CodeInsightUtils.findElementsOfClassInRange(file, start, end, KtExpression::class.java)
|
||||
.map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) }
|
||||
.filterIsInstance<KtCallExpression>()
|
||||
.filter { filter(it) }
|
||||
.toSet()
|
||||
|
||||
// It is necessary to check range because of multiline assign
|
||||
var linesRange = lineNumber..lineNumber
|
||||
return allFilteredCalls.filter {
|
||||
val shouldInclude = it.getLineNumber() in linesRange
|
||||
if (shouldInclude) {
|
||||
linesRange = min(linesRange.start, it.getLineNumber())..max(linesRange.endInclusive, it.getLineNumber(false))
|
||||
}
|
||||
shouldInclude
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Action(
|
||||
val position: XSourcePositionImpl? = null,
|
||||
val stepOverInlineData: StepOverFilterData? = null
|
||||
) {
|
||||
class STEP_OVER : Action() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
|
||||
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext)
|
||||
}
|
||||
|
||||
class STEP_OUT : Action() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
|
||||
debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext)
|
||||
}
|
||||
|
||||
class RUN_TO_CURSOR(position: XSourcePositionImpl) : Action(position) {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
return runReadAction {
|
||||
debugProcess.createRunToCursorCommand(suspendContext, position!!, ignoreBreakpoints)
|
||||
}.contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
class STEP_OVER_INLINED(stepOverInlineData: StepOverFilterData) : Action(stepOverInlineData = stepOverInlineData) {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction(
|
||||
KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!)
|
||||
).contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean)
|
||||
}
|
||||
|
||||
interface KotlinMethodFilter : MethodFilter {
|
||||
fun locationMatches(context: SuspendContextImpl, location: Location): Boolean
|
||||
}
|
||||
|
||||
fun getStepOverAction(
|
||||
location: Location,
|
||||
kotlinSourcePosition: KotlinSteppingCommandProvider.KotlinSourcePosition,
|
||||
frameProxy: StackFrameProxyImpl,
|
||||
isDexDebug: Boolean
|
||||
): Action {
|
||||
val inlineArgumentsToSkip = runReadAction {
|
||||
getInlineCallFunctionArgumentsIfAny(kotlinSourcePosition.sourcePosition)
|
||||
}
|
||||
|
||||
return getStepOverAction(
|
||||
location, kotlinSourcePosition.file, kotlinSourcePosition.linesRange,
|
||||
inlineArgumentsToSkip, frameProxy, isDexDebug
|
||||
)
|
||||
}
|
||||
|
||||
fun getStepOverAction(
|
||||
location: Location,
|
||||
sourceFile: KtFile,
|
||||
range: IntRange,
|
||||
inlineFunctionArguments: List<KtElement>,
|
||||
frameProxy: StackFrameProxyImpl,
|
||||
isDexDebug: Boolean
|
||||
): Action {
|
||||
location.declaringType() ?: return Action.STEP_OVER()
|
||||
|
||||
val project = sourceFile.project
|
||||
|
||||
val methodLocations = location.method().safeAllLineLocations()
|
||||
if (methodLocations.isEmpty()) {
|
||||
return Action.STEP_OVER()
|
||||
}
|
||||
|
||||
val locationsLineAndFile = methodLocations.keysToMap { ktLocationInfo(it, isDexDebug, project, true) }
|
||||
|
||||
fun Location.ktLineNumber(): Int = (locationsLineAndFile[this] ?: ktLocationInfo(this, isDexDebug, project, true)).first
|
||||
fun Location.ktFileName(): String {
|
||||
val ktFile = (locationsLineAndFile[this] ?: ktLocationInfo(this, isDexDebug, project, true)).second
|
||||
// File is not null only for inlined locations. Get file name from debugger information otherwise.
|
||||
return ktFile?.name ?: this.sourceName(KOTLIN_STRATA_NAME)
|
||||
}
|
||||
|
||||
fun isThisMethodLocation(nextLocation: Location): Boolean {
|
||||
if (nextLocation.method() != location.method()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val ktLineNumber = nextLocation.ktLineNumber()
|
||||
if (ktLineNumber !in range) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
return nextLocation.ktFileName() == sourceFile.name
|
||||
} catch (e: AbsentInformationException) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun isBackEdgeLocation(): Boolean {
|
||||
val previousSuitableLocation = methodLocations.reversed()
|
||||
.dropWhile { it != location }
|
||||
.drop(1)
|
||||
.filter(::isThisMethodLocation)
|
||||
.dropWhile { it.ktLineNumber() == location.ktLineNumber() }
|
||||
.firstOrNull()
|
||||
|
||||
return previousSuitableLocation != null && previousSuitableLocation.ktLineNumber() > location.ktLineNumber()
|
||||
}
|
||||
|
||||
val patchedLocation = if (isBackEdgeLocation()) {
|
||||
// Pretend we had already done a backing step
|
||||
methodLocations
|
||||
.filter(::isThisMethodLocation)
|
||||
.firstOrNull { it.ktLineNumber() == location.ktLineNumber() } ?: location
|
||||
} else {
|
||||
location
|
||||
}
|
||||
|
||||
val patchedLineNumber = patchedLocation.ktLineNumber()
|
||||
|
||||
val lambdaArgumentRanges = runReadAction {
|
||||
inlineFunctionArguments.map {
|
||||
val startLineNumber = it.getLineNumber(true) + 1
|
||||
val endLineNumber = it.getLineNumber(false) + 1
|
||||
|
||||
startLineNumber..endLineNumber
|
||||
}
|
||||
}
|
||||
|
||||
val inlineRangeVariables = getInlineRangeLocalVariables(frameProxy)
|
||||
|
||||
// Try to find the range for step over:
|
||||
// - Lines from other files and from functions that are not in range of current one are definitely inlined and should be stepped over.
|
||||
// - Lines in function arguments of inlined functions are inlined too as we found them starting from the position of inlined call.
|
||||
// - Current line locations should also be stepped over.
|
||||
//
|
||||
// We might erroneously extend this range too much when there's a call of function argument or other
|
||||
// inline function in last statement of inline function. The list of inlineRangeVariables will be used later to overcome it.
|
||||
val stepOverLocations = methodLocations
|
||||
.dropWhile { it != patchedLocation }
|
||||
.drop(1)
|
||||
.dropWhile { it.ktLineNumber() == patchedLineNumber }
|
||||
.takeWhile { loc ->
|
||||
!isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it } || loc.ktLineNumber() == patchedLineNumber
|
||||
}
|
||||
|
||||
if (!stepOverLocations.isEmpty()) {
|
||||
// Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that after dexing have a strange artifact.
|
||||
// GOTO instructions are moved to the end of method and as they don't have proper line, line is obtained from the previous
|
||||
// instruction. It might be method return or previous GOTO from the inlining. Simple stepping over such function is really
|
||||
// terrible. On each iteration position jumps to the method end or some previous inline call and then returns back. To prevent
|
||||
// this filter locations with too big code indexes manually
|
||||
val returnCodeIndex: Long = if (isDexDebug) {
|
||||
val method = location.method()
|
||||
val locationsOfLine = method.safeLocationsOfLine(range.last)
|
||||
if (locationsOfLine.isNotEmpty()) {
|
||||
locationsOfLine.map { it.codeIndex() }.max() ?: -1L
|
||||
} else {
|
||||
findReturnFromDexBytecode(location.method())
|
||||
}
|
||||
} else -1L
|
||||
|
||||
return Action.STEP_OVER_INLINED(
|
||||
StepOverFilterData(
|
||||
patchedLineNumber,
|
||||
stepOverLocations.map { it.ktLineNumber() }.toSet(),
|
||||
inlineRangeVariables,
|
||||
isDexDebug,
|
||||
returnCodeIndex
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return Action.STEP_OVER()
|
||||
}
|
||||
|
||||
fun getStepOutAction(
|
||||
location: Location,
|
||||
suspendContext: SuspendContextImpl,
|
||||
inlineFunctions: List<KtNamedFunction>,
|
||||
inlinedArgument: KtFunctionLiteral?
|
||||
): Action {
|
||||
val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT()
|
||||
|
||||
val locations = computedReferenceType.safeAllLineLocations()
|
||||
val nextLineLocations = locations
|
||||
.dropWhile { it != location }
|
||||
.drop(1)
|
||||
.filter { it.method() == location.method() }
|
||||
.dropWhile { it.lineNumber() == location.lineNumber() }
|
||||
|
||||
if (inlineFunctions.isNotEmpty()) {
|
||||
val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions)
|
||||
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
|
||||
}
|
||||
|
||||
if (inlinedArgument != null) {
|
||||
val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument)
|
||||
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
|
||||
}
|
||||
|
||||
return Action.STEP_OVER()
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
|
||||
locations: List<Location>,
|
||||
inlineFunctionsToSkip: List<KtNamedFunction>
|
||||
): XSourcePositionImpl? {
|
||||
return getNextPositionWithFilter(locations) { offset, elementAt ->
|
||||
if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) {
|
||||
return@getNextPositionWithFilter true
|
||||
}
|
||||
|
||||
getInlineArgumentIfAny(elementAt) != null
|
||||
}
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument(
|
||||
locations: List<Location>,
|
||||
inlinedArgumentToSkip: KtFunctionLiteral
|
||||
): XSourcePositionImpl? {
|
||||
return getNextPositionWithFilter(locations) { offset, _ ->
|
||||
inlinedArgumentToSkip.textRange.contains(offset)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.getNextPositionWithFilter(
|
||||
locations: List<Location>,
|
||||
skip: (Int, PsiElement) -> Boolean
|
||||
): XSourcePositionImpl? {
|
||||
for (location in locations) {
|
||||
val position = runReadAction l@{
|
||||
val sourcePosition = try {
|
||||
this.debugProcess.positionManager.getSourcePosition(location)
|
||||
} catch (e: NoDataException) {
|
||||
null
|
||||
} ?: return@l null
|
||||
|
||||
val file = sourcePosition.file as? KtFile ?: return@l null
|
||||
val elementAt = sourcePosition.elementAt ?: return@l null
|
||||
val currentLine = location.lineNumber() - 1
|
||||
val lineStartOffset = file.getLineStartOffset(currentLine) ?: return@l null
|
||||
if (skip(lineStartOffset, elementAt)) return@l null
|
||||
|
||||
XSourcePositionImpl.createByElement(elementAt)
|
||||
}
|
||||
if (position != null) {
|
||||
return position
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun getInlineRangeLocalVariables(stackFrame: StackFrameProxyImpl): List<LocalVariable> {
|
||||
return stackFrame.safeVisibleVariables()
|
||||
.filter {
|
||||
val name = it.name()
|
||||
name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
|
||||
}
|
||||
.map { it.variable }
|
||||
}
|
||||
|
||||
private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? {
|
||||
val functionLiteralExpression = elementAt?.getParentOfType<KtLambdaExpression>(false) ?: return null
|
||||
|
||||
val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL)
|
||||
if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null
|
||||
|
||||
return functionLiteralExpression.functionLiteral
|
||||
}
|
||||
|
||||
private fun findReturnFromDexBytecode(method: Method): Long {
|
||||
val methodLocations = method.safeAllLineLocations()
|
||||
if (methodLocations.isEmpty()) {
|
||||
return -1L
|
||||
}
|
||||
|
||||
var lastMethodCodeIndex = methodLocations.last().codeIndex()
|
||||
// Continue while it's possible to get location
|
||||
while (true) {
|
||||
if (method.locationOfCodeIndex(lastMethodCodeIndex + 1) != null) {
|
||||
lastMethodCodeIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var returnIndex = lastMethodCodeIndex + 1
|
||||
|
||||
val bytecode = method.bytecodes()
|
||||
var i = bytecode.size
|
||||
|
||||
while (i >= 2) {
|
||||
// Can step only through two-byte instructions and abort on any unknown one
|
||||
i -= 2
|
||||
returnIndex -= 1
|
||||
|
||||
val instruction = bytecode[i].toInt()
|
||||
|
||||
if (instruction == RETURN_VOID || instruction == RETURN || instruction == RETURN_WIDE || instruction == RETURN_OBJECT) {
|
||||
// Instruction found
|
||||
return returnIndex
|
||||
} else if (instruction == MOVE || instruction == GOTO) {
|
||||
// proceed
|
||||
} else {
|
||||
// Don't know the instruction and it's length. Abort.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return -1L
|
||||
}
|
||||
|
||||
object DexBytecode {
|
||||
val RETURN_VOID = 0x0e
|
||||
val RETURN = 0x0f
|
||||
val RETURN_WIDE = 0x10
|
||||
val RETURN_OBJECT = 0x11
|
||||
|
||||
val GOTO = 0x28
|
||||
val MOVE = 0x01
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingConfigurableUi">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="99d36" class="javax.swing.JCheckBox" binding="ignoreKotlinMethods">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="false"/>
|
||||
<text resource-bundle="org/jetbrains/kotlin/idea/KotlinBundle" key="debugger.filter.ignore.internal.classes"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="c37da">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.stepping;
|
||||
|
||||
|
||||
import com.intellij.openapi.options.ConfigurableUi;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class KotlinSteppingConfigurableUi implements ConfigurableUi<KotlinDebuggerSettings> {
|
||||
private JCheckBox ignoreKotlinMethods;
|
||||
private JPanel myPanel;
|
||||
|
||||
@Override
|
||||
public void reset(@NotNull KotlinDebuggerSettings settings) {
|
||||
boolean flag = settings.getDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES();
|
||||
ignoreKotlinMethods.setSelected(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified(@NotNull KotlinDebuggerSettings settings) {
|
||||
return settings.getDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES() != ignoreKotlinMethods.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(@NotNull KotlinDebuggerSettings settings) {
|
||||
settings.setDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES(ignoreKotlinMethods.isSelected());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myPanel;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.debugger.engine.RequestHint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.util.Range
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import org.jetbrains.kotlin.idea.debugger.isOnSuspendReturnOrReenter
|
||||
import org.jetbrains.kotlin.idea.debugger.suspendFunctionFirstLineLocation
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
|
||||
class KotlinSuspendCallStepOverFilter(
|
||||
private val line: Int,
|
||||
private val file: PsiFile,
|
||||
private val ignoreBreakpoints: Boolean
|
||||
) : MethodFilter {
|
||||
override fun getCallingExpressionLines(): Range<Int>? = Range(line, line)
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location?): Boolean {
|
||||
return location != null && isOnSuspendReturnOrReenter(location)
|
||||
}
|
||||
|
||||
override fun onReached(context: SuspendContextImpl, hint: RequestHint): Int {
|
||||
val location = context.frameProxy?.location() ?: return RequestHint.STOP
|
||||
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return RequestHint.STOP
|
||||
|
||||
val debugProcess = context.debugProcess
|
||||
val breakpointManager = DebuggerManagerEx.getInstanceEx(debugProcess.project).breakpointManager
|
||||
breakpointManager.applyThreadFilter(debugProcess, null)
|
||||
|
||||
createRunToCursorBreakpoint(context, suspendStartLineNumber - 1, file, ignoreBreakpoints)
|
||||
return RequestHint.RESUME
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRunToCursorBreakpoint(context: SuspendContextImpl, line: Int, file: PsiFile, ignoreBreakpoints: Boolean) {
|
||||
val position = XSourcePositionImpl.create(file.virtualFile, line) ?: return
|
||||
val process = context.debugProcess
|
||||
process.showStatusText(DebuggerBundle.message("status.run.to.cursor"))
|
||||
process.cancelRunToCursorBreakpoint()
|
||||
|
||||
if (ignoreBreakpoints) {
|
||||
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.disableBreakpoints(process)
|
||||
}
|
||||
|
||||
val runToCursorBreakpoint =
|
||||
runReadAction {
|
||||
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, ignoreBreakpoints)
|
||||
} ?: return
|
||||
|
||||
runToCursorBreakpoint.suspendPolicy = when {
|
||||
context.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD -> DebuggerSettings.SUSPEND_THREAD
|
||||
else -> DebuggerSettings.SUSPEND_ALL
|
||||
}
|
||||
|
||||
runToCursorBreakpoint.createRequest(process)
|
||||
process.setRunToCursorBreakpoint(runToCursorBreakpoint)
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.BreakpointStepMethodFilter
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.debugger.engine.RequestHint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.sun.jdi.VMDisconnectedException
|
||||
import com.sun.jdi.request.StepRequest
|
||||
import org.intellij.lang.annotations.MagicConstant
|
||||
import java.lang.reflect.Field
|
||||
|
||||
internal class RequestHintWithMethodFilter(
|
||||
stepThread: ThreadReferenceProxyImpl,
|
||||
suspendContext: SuspendContextImpl,
|
||||
@MagicConstant(
|
||||
intValues = longArrayOf(
|
||||
StepRequest.STEP_INTO.toLong(),
|
||||
StepRequest.STEP_OVER.toLong(),
|
||||
StepRequest.STEP_OUT.toLong()
|
||||
)
|
||||
) depth: Int,
|
||||
methodFilter: MethodFilter
|
||||
) : RequestHint(stepThread, suspendContext, methodFilter) {
|
||||
private var targetMethodMatched = false
|
||||
|
||||
init {
|
||||
// NOTE: Debugger API. Open RequestHint constructor with depth
|
||||
if (depth != StepRequest.STEP_INTO) {
|
||||
findFieldWithValue(StepRequest.STEP_INTO, Integer.TYPE)?.setInt(this, depth)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findFieldWithValue(value: Int, type: Class<*>): Field? {
|
||||
return RequestHint::class.java.declaredFields.firstOrNull { field ->
|
||||
if (field.type == type) {
|
||||
field.isAccessible = true
|
||||
if (field.getInt(this) == value) {
|
||||
return@firstOrNull true
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNextStepDepth(context: SuspendContextImpl): Int {
|
||||
try {
|
||||
val frameProxy = context.frameProxy
|
||||
val filter = methodFilter
|
||||
|
||||
if (filter != null && frameProxy != null && filter !is BreakpointStepMethodFilter) {
|
||||
/*NODE: Debugger API. Base implementation works only for smart step into, and calls filter only if !isTheSameFrame(context). */
|
||||
if (filter.locationMatches(context.debugProcess, frameProxy.location())) {
|
||||
targetMethodMatched = true
|
||||
return filter.onReached(context, this)
|
||||
}
|
||||
}
|
||||
} catch (ignored: VMDisconnectedException) {
|
||||
return STOP
|
||||
} catch (e: EvaluateException) {
|
||||
LOG.error(e)
|
||||
return STOP
|
||||
}
|
||||
|
||||
return super.getNextStepDepth(context)
|
||||
}
|
||||
|
||||
override fun wasStepTargetMethodMatched(): Boolean {
|
||||
return super.wasStepTargetMethodMatched() || targetMethodMatched
|
||||
}
|
||||
}
|
||||
|
||||
private val LOG = Logger.getInstance(RequestHintWithMethodFilter::class.java)
|
||||
@@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile(project(":compiler:backend"))
|
||||
compile(project(":idea:jvm-debugger:eval4j"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:idea-j2k"))
|
||||
compile(project(":idea:jvm-debugger:jvm-debugger-util"))
|
||||
compile(files("${System.getProperty("java.home")}/../lib/tools.jar"))
|
||||
|
||||
testCompile(project(":kotlin-test:kotlin-test-junit"))
|
||||
testCompile(commonDep("junit:junit"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { none() }
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.completion.CompletionInformationProvider
|
||||
|
||||
class DebuggerFieldCompletionInformationProvider : CompletionInformationProvider {
|
||||
override fun getContainerAndReceiverInformation(descriptor: DeclarationDescriptor) =
|
||||
(descriptor as? DebuggerFieldPropertyDescriptor)?.description?.let { " $it" }
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
|
||||
|
||||
class DebuggerFieldExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
|
||||
val propertyDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return null
|
||||
|
||||
if (propertyDescriptor is DebuggerFieldPropertyDescriptor) {
|
||||
return StackValue.StackValueWithSimpleReceiver.field(
|
||||
c.typeMapper.mapType(propertyDescriptor.type),
|
||||
propertyDescriptor.ownerType(c.codegen.state),
|
||||
propertyDescriptor.fieldName,
|
||||
false,
|
||||
receiver
|
||||
)
|
||||
}
|
||||
|
||||
if (propertyDescriptor is JavaPropertyDescriptor) {
|
||||
val containingClass = propertyDescriptor.containingDeclaration as? JavaClassDescriptor
|
||||
if (containingClass != null) {
|
||||
val correspondingGetter = JavaSyntheticPropertiesScope(LockBasedStorageManager.NO_LOCKS, LookupTracker.DO_NOTHING)
|
||||
.getSyntheticExtensionProperties(listOf(containingClass.defaultType), NoLookupLocation.FROM_BACKEND)
|
||||
.firstOrNull { it.name == propertyDescriptor.name }
|
||||
|
||||
if (correspondingGetter != null) {
|
||||
return c.codegen.intermediateValueForProperty(
|
||||
correspondingGetter, false, false,
|
||||
c.codegen.getSuperCallTarget(resolvedCall.call),
|
||||
false, receiver, resolvedCall, false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.extension.KotlinIndicesHelperExtension
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class DebuggerFieldKotlinIndicesHelperExtension : KotlinIndicesHelperExtension {
|
||||
override fun appendExtensionCallables(
|
||||
consumer: MutableList<in CallableDescriptor>,
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
receiverTypes: Collection<KotlinType>,
|
||||
nameFilter: (String) -> Boolean,
|
||||
lookupLocation: LookupLocation
|
||||
) {
|
||||
val javaPropertiesScope = JavaSyntheticPropertiesScope(LockBasedStorageManager.NO_LOCKS, LookupTracker.DO_NOTHING)
|
||||
val fieldScope = DebuggerFieldSyntheticScope(javaPropertiesScope)
|
||||
|
||||
for (property in fieldScope.getSyntheticExtensionProperties(receiverTypes, lookupLocation)) {
|
||||
if (nameFilter(property.name.asString())) {
|
||||
consumer += property
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun appendExtensionCallables(
|
||||
consumer: MutableList<in CallableDescriptor>,
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
receiverTypes: Collection<KotlinType>,
|
||||
nameFilter: (String) -> Boolean
|
||||
) {
|
||||
throw IllegalStateException("Should not be called")
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
|
||||
import org.jetbrains.kotlin.idea.project.platform
|
||||
import org.jetbrains.kotlin.incremental.KotlinLookupLocation
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.load.java.components.JavaSourceElementFactoryImpl
|
||||
import org.jetbrains.kotlin.load.java.components.TypeUsage
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.types.toAttributes
|
||||
import org.jetbrains.kotlin.load.java.structure.classId
|
||||
import org.jetbrains.kotlin.load.kotlin.internalName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.DescriptorFactory
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
import org.jetbrains.kotlin.platform.isCommon
|
||||
import org.jetbrains.kotlin.platform.jvm.isJvm
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.SyntheticScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
||||
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
|
||||
import org.jetbrains.kotlin.synthetic.SyntheticScopeProviderExtension
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class DebuggerFieldSyntheticScopeProvider : SyntheticScopeProviderExtension {
|
||||
override fun getScopes(
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
javaSyntheticPropertiesScope: JavaSyntheticPropertiesScope
|
||||
): List<SyntheticScope> {
|
||||
return listOf<SyntheticScope>(DebuggerFieldSyntheticScope(javaSyntheticPropertiesScope))
|
||||
}
|
||||
}
|
||||
|
||||
class DebuggerFieldSyntheticScope(val javaSyntheticPropertiesScope: JavaSyntheticPropertiesScope) : SyntheticScope.Default() {
|
||||
private val javaSourceElementFactory = JavaSourceElementFactoryImpl()
|
||||
|
||||
override fun getSyntheticExtensionProperties(
|
||||
receiverTypes: Collection<KotlinType>,
|
||||
name: Name,
|
||||
location: LookupLocation
|
||||
): Collection<PropertyDescriptor> {
|
||||
return getSyntheticExtensionProperties(receiverTypes, location).filter { it.name == name }
|
||||
}
|
||||
|
||||
override fun getSyntheticExtensionProperties(
|
||||
receiverTypes: Collection<KotlinType>,
|
||||
location: LookupLocation
|
||||
): Collection<PropertyDescriptor> {
|
||||
if (!isInEvaluator(location)) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val result = mutableListOf<PropertyDescriptor>()
|
||||
for (type in receiverTypes) {
|
||||
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor ?: continue
|
||||
result += getSyntheticPropertiesForClass(clazz)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun isInEvaluator(location: LookupLocation): Boolean {
|
||||
val element = (location as? KotlinLookupLocation)?.element ?: return false
|
||||
val containingFile = element.containingFile?.takeIf { it.isValid } as? KtFile ?: return false
|
||||
|
||||
val platform = containingFile.platform
|
||||
if (!platform.isJvm() && !platform.isCommon()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return containingFile is KtCodeFragment
|
||||
}
|
||||
|
||||
private fun getSyntheticPropertiesForClass(clazz: ClassDescriptor): Collection<PropertyDescriptor> {
|
||||
val collected = mutableMapOf<Name, PropertyDescriptor>()
|
||||
|
||||
val syntheticPropertyNames = javaSyntheticPropertiesScope
|
||||
.getSyntheticExtensionProperties(listOf(clazz.defaultType), NoLookupLocation.FROM_SYNTHETIC_SCOPE)
|
||||
.mapTo(mutableSetOf()) { it.name }
|
||||
|
||||
collectPropertiesWithParent(clazz, syntheticPropertyNames, collected)
|
||||
return collected.values
|
||||
}
|
||||
|
||||
private tailrec fun collectPropertiesWithParent(
|
||||
clazz: ClassDescriptor,
|
||||
syntheticNames: Set<Name>,
|
||||
consumer: MutableMap<Name, PropertyDescriptor>
|
||||
) {
|
||||
when (clazz) {
|
||||
is LazyJavaClassDescriptor -> collectJavaProperties(clazz, syntheticNames, consumer)
|
||||
is JavaClassDescriptor -> error("Unsupported Java class type")
|
||||
else -> collectKotlinProperties(clazz, consumer)
|
||||
}
|
||||
|
||||
val superClass = clazz.getSuperClassNotAny()
|
||||
if (superClass != null) {
|
||||
collectPropertiesWithParent(superClass, syntheticNames, consumer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectKotlinProperties(clazz: ClassDescriptor, consumer: MutableMap<Name, PropertyDescriptor>) {
|
||||
for (descriptor in clazz.unsubstitutedMemberScope.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES)) {
|
||||
val propertyDescriptor = descriptor as? PropertyDescriptor ?: continue
|
||||
val name = propertyDescriptor.name
|
||||
if (propertyDescriptor.backingField == null || name in consumer) continue
|
||||
|
||||
val type = propertyDescriptor.type
|
||||
val sourceElement = propertyDescriptor.source
|
||||
|
||||
consumer[name] = createSyntheticPropertyDescriptor(clazz, type, name.asString(), "Backing field", sourceElement) { state ->
|
||||
state.typeMapper.mapType(clazz.defaultType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectJavaProperties(
|
||||
clazz: LazyJavaClassDescriptor,
|
||||
syntheticNames: Set<Name>,
|
||||
consumer: MutableMap<Name, PropertyDescriptor>
|
||||
) {
|
||||
val javaClass = clazz.jClass
|
||||
|
||||
for (field in javaClass.fields) {
|
||||
val fieldName = field.name
|
||||
if (field.isEnumEntry || field.isStatic || fieldName in consumer || fieldName !in syntheticNames) continue
|
||||
|
||||
val ownerClassName = javaClass.classId?.internalName ?: continue
|
||||
val typeResolver = clazz.outerContext.typeResolver
|
||||
|
||||
val type = typeResolver.transformJavaType(field.type, TypeUsage.COMMON.toAttributes()).replaceArgumentsWithStarProjections()
|
||||
val sourceElement = javaSourceElementFactory.source(field)
|
||||
|
||||
consumer[fieldName] = createSyntheticPropertyDescriptor(clazz, type, fieldName.asString(), "Java field", sourceElement) {
|
||||
Type.getObjectType(ownerClassName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSyntheticPropertyDescriptor(
|
||||
clazz: ClassDescriptor,
|
||||
type: KotlinType,
|
||||
fieldName: String,
|
||||
description: String,
|
||||
getterSource: SourceElement,
|
||||
ownerType: (GenerationState) -> Type
|
||||
): PropertyDescriptor {
|
||||
val propertyDescriptor = DebuggerFieldPropertyDescriptor(clazz, fieldName, description, ownerType)
|
||||
|
||||
val extensionReceiverParameter = DescriptorFactory.createExtensionReceiverParameterForCallable(
|
||||
propertyDescriptor,
|
||||
clazz.defaultType.replaceArgumentsWithStarProjections(),
|
||||
Annotations.EMPTY
|
||||
)
|
||||
|
||||
propertyDescriptor.setType(type, emptyList(), null, extensionReceiverParameter)
|
||||
|
||||
val getter = PropertyGetterDescriptorImpl(
|
||||
propertyDescriptor, Annotations.EMPTY, Modality.FINAL,
|
||||
Visibilities.PUBLIC, false, false, false,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
null, getterSource
|
||||
)
|
||||
|
||||
propertyDescriptor.initialize(getter, null)
|
||||
|
||||
return propertyDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
internal class DebuggerFieldPropertyDescriptor(
|
||||
containingDeclaration: DeclarationDescriptor,
|
||||
val fieldName: String,
|
||||
val description: String,
|
||||
val ownerType: (GenerationState) -> Type
|
||||
) : PropertyDescriptorImpl(
|
||||
containingDeclaration,
|
||||
null,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PUBLIC,
|
||||
/*isVar = */true,
|
||||
Name.identifier(fieldName + "_field"),
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
SourceElement.NO_SOURCE,
|
||||
/*lateInit = */false,
|
||||
/*isConst = */false,
|
||||
/*isExpect = */false,
|
||||
/*isActual = */false,
|
||||
/*isExternal = */false,
|
||||
/*isDelegated = */false
|
||||
)
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.PsiTypesUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.util.concurrency.Semaphore
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.core.util.getKotlinJvmRuntimeMarkerClass
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.getClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.getContextElement
|
||||
import org.jetbrains.kotlin.idea.j2k.JavaToKotlinConverterFactory
|
||||
import org.jetbrains.kotlin.idea.j2k.convertToKotlin
|
||||
import org.jetbrains.kotlin.idea.j2k.j2k
|
||||
import org.jetbrains.kotlin.idea.j2k.j2kText
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||
val contextElement = getContextElement(context)
|
||||
|
||||
val constructor = when (item.kind) {
|
||||
null -> error("Code fragment kind should be set")
|
||||
CodeFragmentKind.EXPRESSION -> ::KtExpressionCodeFragment
|
||||
CodeFragmentKind.CODE_BLOCK -> ::KtBlockCodeFragment
|
||||
}
|
||||
|
||||
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
|
||||
supplyDebugLabels(codeFragment, context)
|
||||
|
||||
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
|
||||
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val debuggerSession = debuggerContext.debuggerSession
|
||||
if (debuggerSession == null || debuggerContext.suspendContext == null) {
|
||||
null
|
||||
} else {
|
||||
val semaphore = Semaphore()
|
||||
semaphore.down()
|
||||
val nameRef = AtomicReference<KotlinType>()
|
||||
val worker = object : KotlinRuntimeTypeEvaluator(
|
||||
null, expression, debuggerContext, ProgressManager.getInstance().progressIndicator!!
|
||||
) {
|
||||
override fun typeCalculationFinished(type: KotlinType?) {
|
||||
nameRef.set(type)
|
||||
semaphore.up()
|
||||
}
|
||||
}
|
||||
|
||||
debuggerContext.debugProcess?.managerThread?.invoke(worker)
|
||||
|
||||
for (i in 0..50) {
|
||||
ProgressManager.checkCanceled()
|
||||
if (semaphore.waitFor(20)) break
|
||||
}
|
||||
|
||||
nameRef.get()
|
||||
}
|
||||
})
|
||||
|
||||
if (contextElement != null && contextElement !is KtElement) {
|
||||
codeFragment.putCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE, {
|
||||
val emptyFile = createFakeFileWithJavaContextElement("", contextElement)
|
||||
|
||||
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val debuggerSession = debuggerContext.debuggerSession
|
||||
if ((debuggerSession == null || debuggerContext.suspendContext == null) && !ApplicationManager.getApplication().isUnitTestMode) {
|
||||
LOG.warn("Couldn't create fake context element for java file, debugger isn't paused on breakpoint")
|
||||
return@putCopyableUserData emptyFile
|
||||
}
|
||||
|
||||
val frameDescriptor = getFrameInfo(contextElement, debuggerContext)
|
||||
if (frameDescriptor == null) {
|
||||
LOG.warn(
|
||||
"Couldn't get info about 'this' and local variables for " +
|
||||
"${debuggerContext.sourcePosition?.file?.name}:${debuggerContext.sourcePosition?.line}"
|
||||
)
|
||||
return@putCopyableUserData emptyFile
|
||||
}
|
||||
|
||||
val receiverTypeReference =
|
||||
frameDescriptor.thisObject?.let { createKotlinProperty(project, FAKE_JAVA_THIS_NAME, it.type().name(), it) }?.typeReference
|
||||
val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
|
||||
|
||||
val kotlinVariablesText =
|
||||
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
|
||||
|
||||
val fakeFunctionText = "fun ${receiverTypeText}$FAKE_JAVA_CONTEXT_FUNCTION_NAME() {\n$kotlinVariablesText\n}"
|
||||
|
||||
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
|
||||
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
||||
val fakeContext = fakeFunction?.bodyBlockExpression?.statements?.lastOrNull()
|
||||
|
||||
return@putCopyableUserData fakeContext ?: emptyFile
|
||||
})
|
||||
}
|
||||
|
||||
return codeFragment
|
||||
}
|
||||
|
||||
private fun supplyDebugLabels(codeFragment: KtCodeFragment, context: PsiElement?) {
|
||||
val project = codeFragment.project
|
||||
val debugProcess = getDebugProcess(project, context) ?: return
|
||||
DebugLabelPropertyDescriptorProvider(codeFragment, debugProcess).supplyDebugLabels()
|
||||
}
|
||||
|
||||
private fun getDebugProcess(project: Project, context: PsiElement?): DebugProcessImpl? {
|
||||
return if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
context?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess
|
||||
} else {
|
||||
DebuggerManagerEx.getInstanceEx(project).context.debugProcess
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFrameInfo(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): FrameInfo? {
|
||||
val semaphore = Semaphore()
|
||||
semaphore.down()
|
||||
|
||||
var frameInfo: FrameInfo? = null
|
||||
|
||||
val worker = object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
try {
|
||||
val frame = if (ApplicationManager.getApplication().isUnitTestMode)
|
||||
contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.frameProxy?.stackFrame
|
||||
else
|
||||
debuggerContext.frameProxy?.stackFrame
|
||||
|
||||
val visibleVariables = if (frame != null) {
|
||||
val values = frame.getValues(frame.visibleVariables())
|
||||
values.filterValues { it != null }
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
|
||||
} catch (ignored: AbsentInformationException) {
|
||||
// Debug info unavailable
|
||||
} catch (ignored: InvalidStackFrameException) {
|
||||
// Thread is resumed, the frame we have is not valid anymore
|
||||
} finally {
|
||||
semaphore.up()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debuggerContext.debugProcess?.managerThread?.invoke(worker)
|
||||
|
||||
for (i in 0..50) {
|
||||
if (semaphore.waitFor(20)) break
|
||||
}
|
||||
|
||||
return frameInfo
|
||||
}
|
||||
|
||||
private class FrameInfo(val thisObject: Value?, val visibleVariables: Map<LocalVariable, Value>)
|
||||
|
||||
private fun initImports(imports: String?): String? {
|
||||
if (imports != null && !imports.isEmpty()) {
|
||||
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
|
||||
.mapNotNull { fixImportIfNeeded(it) }
|
||||
.joinToString(KtCodeFragment.IMPORT_SEPARATOR)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun fixImportIfNeeded(import: String): String? {
|
||||
// skip arrays
|
||||
if (import.endsWith("[]")) {
|
||||
return fixImportIfNeeded(import.removeSuffix("[]").trim())
|
||||
}
|
||||
|
||||
// skip primitive types
|
||||
if (PsiTypesUtil.boxIfPossible(import) != import) {
|
||||
return null
|
||||
}
|
||||
return import
|
||||
}
|
||||
|
||||
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||
val kotlinCodeFragment = createCodeFragment(item, context, project)
|
||||
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
|
||||
val javaExpression = try {
|
||||
PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
null
|
||||
}
|
||||
|
||||
val importList = try {
|
||||
kotlinCodeFragment.importsAsImportList()?.let {
|
||||
(PsiFileFactory.getInstance(project).createFileFromText(
|
||||
"dummy.java", JavaFileType.INSTANCE, it.text
|
||||
) as? PsiJavaFile)?.importList
|
||||
}
|
||||
} catch (e: IncorrectOperationException) {
|
||||
null
|
||||
}
|
||||
|
||||
if (javaExpression != null && !PsiTreeUtil.hasErrorElements(javaExpression)) {
|
||||
var convertedFragment: KtExpressionCodeFragment? = null
|
||||
project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") {
|
||||
try {
|
||||
val (elementResults, _, conversionContext) = javaExpression.convertToKotlin() ?: return@executeWriteCommand
|
||||
val newText = elementResults.singleOrNull()?.text
|
||||
val newImports = importList?.j2kText()
|
||||
if (newText != null) {
|
||||
convertedFragment = KtExpressionCodeFragment(
|
||||
project,
|
||||
kotlinCodeFragment.name,
|
||||
newText,
|
||||
newImports,
|
||||
kotlinCodeFragment.context
|
||||
)
|
||||
|
||||
AfterConversionPass(project, JavaToKotlinConverterFactory.createPostProcessor(formatCode = false))
|
||||
.run(
|
||||
convertedFragment!!,
|
||||
conversionContext,
|
||||
range = null
|
||||
)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// ignored because text can be invalid
|
||||
LOG.error("Couldn't convert expression:\n`${javaExpression.text}`", e)
|
||||
}
|
||||
}
|
||||
return convertedFragment ?: kotlinCodeFragment
|
||||
}
|
||||
}
|
||||
return kotlinCodeFragment
|
||||
}
|
||||
|
||||
override fun isContextAccepted(contextElement: PsiElement?): Boolean = runReadAction {
|
||||
when {
|
||||
// PsiCodeBlock -> DummyHolder -> originalElement
|
||||
contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context)
|
||||
contextElement == null -> false
|
||||
contextElement.language == KotlinFileType.INSTANCE.language -> true
|
||||
contextElement.language == JavaFileType.INSTANCE.language -> {
|
||||
getKotlinJvmRuntimeMarkerClass(contextElement.project, contextElement.resolveScope) != null
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFileType(): KotlinFileType = KotlinFileType.INSTANCE
|
||||
|
||||
override fun getEvaluatorBuilder() = KotlinEvaluatorBuilder
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(this::class.java)
|
||||
|
||||
@get:TestOnly
|
||||
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
|
||||
|
||||
const val FAKE_JAVA_CONTEXT_FUNCTION_NAME = "_java_locals_debug_fun_"
|
||||
const val FAKE_JAVA_THIS_NAME = "\$this\$_java_locals_debug_fun_"
|
||||
|
||||
private fun Map<String, Value>.kotlinVariablesAsText(project: Project): String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
val psiNameHelper = PsiNameHelper.getInstance(project)
|
||||
for ((variableName, variableValue) in entries) {
|
||||
if (!psiNameHelper.isIdentifier(variableName)) continue
|
||||
|
||||
val variableTypeName = variableValue.type()?.name() ?: continue
|
||||
|
||||
val kotlinProperty = createKotlinProperty(project, variableName, variableTypeName, variableValue) ?: continue
|
||||
|
||||
sb.append("${kotlinProperty.text}\n")
|
||||
}
|
||||
|
||||
sb.append("val _debug_context_val = 1\n")
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): KtProperty? {
|
||||
val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project))
|
||||
if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) {
|
||||
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable())
|
||||
return KtPsiFactory(project).createProperty(variableName.quoteIfNeeded(), renderedType, false)
|
||||
}
|
||||
|
||||
fun String.addArraySuffix() = if (value is ArrayReference) this + "[]" else this
|
||||
|
||||
val className = variableTypeName.replace("$", ".").substringBefore("[]")
|
||||
val classType = PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project))
|
||||
val type = (if (value !is PrimitiveValue && classType.resolve() == null)
|
||||
CommonClassNames.JAVA_LANG_OBJECT
|
||||
else
|
||||
className).addArraySuffix()
|
||||
|
||||
val field = PsiElementFactory.SERVICE.getInstance(project)
|
||||
.createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project)))
|
||||
val ktField = field.j2k() as? KtProperty
|
||||
ktField?.modifierList?.delete()
|
||||
return ktField
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {
|
||||
val javaFile = javaContext.containingFile as? PsiJavaFile
|
||||
|
||||
val sb = StringBuilder()
|
||||
|
||||
javaFile?.packageName?.takeUnless { it.isBlank() }?.let {
|
||||
sb.append("package ").append(it.quoteIfNeeded()).append("\n")
|
||||
}
|
||||
|
||||
javaFile?.importList?.let { sb.append(it.text).append("\n") }
|
||||
|
||||
sb.append(funWithLocalVariables)
|
||||
|
||||
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
|
||||
}
|
||||
}
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.expression.*
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.testFramework.runInEdtAndWait
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.eval4j.*
|
||||
import org.jetbrains.eval4j.Value as Eval4JValue
|
||||
import org.jetbrains.eval4j.jdi.JDIEval
|
||||
import org.jetbrains.eval4j.jdi.asJdiValue
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.eval4j.jdi.makeInitialFrame
|
||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.idea.core.util.attachmentByPsiFile
|
||||
import org.jetbrains.kotlin.idea.core.util.mergeAttachments
|
||||
import org.jetbrains.kotlin.idea.core.util.runInReadActionWithWriteActionPriorityWithPCE
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.compileCodeFragmentCacheAware
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_CLASS_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_FUNCTION_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import java.util.*
|
||||
|
||||
internal val LOG = Logger.getInstance(KotlinEvaluator::class.java)
|
||||
|
||||
object KotlinEvaluatorBuilder : EvaluatorBuilder {
|
||||
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
|
||||
if (codeFragment !is KtCodeFragment) {
|
||||
return EvaluatorBuilderImpl.getInstance().build(codeFragment, position)
|
||||
}
|
||||
|
||||
val context = codeFragment.context ?: evaluationException("Cannot evaluate an expression without a context")
|
||||
val file = context.containingFile
|
||||
|
||||
if (file !is KtFile) {
|
||||
reportError(codeFragment, position, "Unknown context${codeFragment.context?.javaClass}")
|
||||
evaluationException("Couldn't evaluate Kotlin expression in this context")
|
||||
}
|
||||
|
||||
return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment, position))
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: SourcePosition?) : Evaluator {
|
||||
override fun evaluate(context: EvaluationContextImpl): Any? {
|
||||
if (codeFragment.text.isEmpty()) {
|
||||
return context.debugProcess.virtualMachineProxy.mirrorOfVoid()
|
||||
}
|
||||
|
||||
if (DumbService.getInstance(codeFragment.project).isDumb) {
|
||||
evaluationException("Code fragment evaluation is not available in the dumb mode")
|
||||
}
|
||||
|
||||
val frameProxy = context.frameProxy
|
||||
?: evaluationException("Cannot evaluate a code fragment: frame proxy is not available")
|
||||
|
||||
val operatingThread = context.suspendContext.thread
|
||||
?: evaluationException("Cannot evaluate a code fragment: thread is not available")
|
||||
|
||||
if (!operatingThread.isSuspended) {
|
||||
evaluationException("Evaluation is available only for the suspended threads")
|
||||
}
|
||||
|
||||
try {
|
||||
val executionContext = ExecutionContext(context, frameProxy)
|
||||
return evaluateSafe(executionContext)
|
||||
} catch (e: EvaluateException) {
|
||||
throw e
|
||||
} catch (e: ProcessCanceledException) {
|
||||
evaluationException(e)
|
||||
} catch (e: Eval4JInterpretingException) {
|
||||
evaluationException(e.cause)
|
||||
} catch (e: Exception) {
|
||||
val isSpecialException = isSpecialException(e)
|
||||
if (isSpecialException) {
|
||||
evaluationException(e)
|
||||
}
|
||||
|
||||
reportError(codeFragment, sourcePosition, e.message ?: "An exception occurred", e)
|
||||
|
||||
val cause = if (e.message != null) ": ${e.message}" else ""
|
||||
evaluationException("Cannot evaluate the expression: $cause")
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateSafe(context: ExecutionContext): Any? {
|
||||
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context)
|
||||
|
||||
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
|
||||
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
||||
|
||||
val result = if (classLoaderRef != null) {
|
||||
evaluateWithCompilation(context, compiledData, classLoaderRef)
|
||||
?: evaluateWithEval4J(context, compiledData, classLoaderRef)
|
||||
} else {
|
||||
evaluateWithEval4J(context, compiledData, classLoaderRef)
|
||||
}
|
||||
|
||||
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
|
||||
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
|
||||
val (recompiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = true)
|
||||
return evaluateWithEval4J(context, recompiledData, classLoaderRef).toJdiValue(context)
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is InterpreterResult -> result.toJdiValue(context)
|
||||
else -> result
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCodeFragment(context: ExecutionContext): CompiledDataDescriptor {
|
||||
val debugProcess = context.debugProcess
|
||||
var analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||
|
||||
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
|
||||
// Repeat analysis with toString() added
|
||||
analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||
}
|
||||
|
||||
val (bindingContext) = runReadAction {
|
||||
DebuggerUtils.analyzeInlinedFunctions(
|
||||
KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(listOf(codeFragment)),
|
||||
codeFragment, false, analysisResult.bindingContext
|
||||
)
|
||||
}
|
||||
|
||||
val moduleDescriptor = analysisResult.moduleDescriptor
|
||||
|
||||
val result = CodeFragmentCompiler(context).compile(codeFragment, bindingContext, moduleDescriptor)
|
||||
return createCompiledDataDescriptor(result, sourcePosition)
|
||||
}
|
||||
|
||||
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
|
||||
if (this !is KtExpressionCodeFragment) {
|
||||
return false
|
||||
}
|
||||
|
||||
val contentElement = runReadAction { getContentElement() }
|
||||
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type
|
||||
if (contentElement != null && expressionType?.isInlineClassType() == true) {
|
||||
val newExpression = runReadAction {
|
||||
val expressionText = contentElement.text
|
||||
KtPsiFactory(project).createExpression("($expressionText).toString()")
|
||||
}
|
||||
runInEdtAndWait {
|
||||
project.executeWriteCommand("Wrap with 'toString()'") {
|
||||
contentElement.replace(newExpression)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private data class ErrorCheckingResult(
|
||||
val bindingContext: BindingContext,
|
||||
val moduleDescriptor: ModuleDescriptor,
|
||||
val files: List<KtFile>
|
||||
)
|
||||
|
||||
private fun checkForErrors(codeFragment: KtCodeFragment, debugProcess: DebugProcessImpl): ErrorCheckingResult {
|
||||
return runInReadActionWithWriteActionPriorityWithPCE {
|
||||
try {
|
||||
AnalyzingUtils.checkForSyntacticErrors(codeFragment)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
evaluationException(e.message ?: e.toString())
|
||||
}
|
||||
|
||||
val filesToAnalyze = listOf(codeFragment)
|
||||
val resolutionFacade = KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(filesToAnalyze)
|
||||
|
||||
DebugLabelPropertyDescriptorProvider(codeFragment, debugProcess).supplyDebugLabels()
|
||||
|
||||
val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
|
||||
|
||||
if (analysisResult.isError()) {
|
||||
evaluationException(analysisResult.error)
|
||||
}
|
||||
|
||||
val bindingContext = analysisResult.bindingContext
|
||||
|
||||
bindingContext.diagnostics
|
||||
.filter { it.factory !in IGNORED_DIAGNOSTICS }
|
||||
.firstOrNull { it.severity == Severity.ERROR && it.psiElement.containingFile == codeFragment }
|
||||
?.let { evaluationException(DefaultErrorMessages.render(it)) }
|
||||
|
||||
ErrorCheckingResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(codeFragment))
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateWithCompilation(
|
||||
context: ExecutionContext,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference
|
||||
): Value? {
|
||||
return try {
|
||||
runEvaluation(context, compiledData, classLoader) { args ->
|
||||
val mainClassType = context.findClass(GENERATED_CLASS_NAME, classLoader) as? ClassType
|
||||
?: error("Can not find class \"$GENERATED_CLASS_NAME\"")
|
||||
val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_NAME }
|
||||
val returnValue = context.invokeMethod(mainClassType, mainMethod, args)
|
||||
EvaluatorValueConverter(context).unref(returnValue)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
LOG.error("Unable to evaluate the expression with compilation", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateWithEval4J(
|
||||
context: ExecutionContext,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference?
|
||||
): InterpreterResult {
|
||||
val mainClassBytecode = compiledData.mainClass.bytes
|
||||
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) }
|
||||
val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME }
|
||||
|
||||
return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader) { args ->
|
||||
val vm = context.vm.virtualMachine
|
||||
val thread = context.suspendContext.thread?.threadReference?.takeIf { it.isSuspended }
|
||||
?: error("Can not find a thread to run evaluation on")
|
||||
|
||||
val eval = JDIEval(vm, classLoader, thread, context.invokePolicy)
|
||||
interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> runEvaluation(
|
||||
context: ExecutionContext,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference?,
|
||||
block: (List<Value?>) -> T
|
||||
): T {
|
||||
// Preload additional classes
|
||||
compiledData.classes
|
||||
.filter { !it.isMainClass }
|
||||
.forEach { context.findClass(it.className, classLoader) }
|
||||
|
||||
return context.vm.virtualMachine.executeWithBreakpointsDisabled {
|
||||
for (parameterType in compiledData.mainMethodSignature.parameterTypes) {
|
||||
context.findClass(parameterType, classLoader)
|
||||
}
|
||||
val args = calculateMainMethodCallArguments(context, compiledData)
|
||||
block(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateMainMethodCallArguments(context: ExecutionContext, compiledData: CompiledDataDescriptor): List<Value?> {
|
||||
val asmValueParameters = compiledData.mainMethodSignature.parameterTypes
|
||||
val valueParameters = compiledData.parameters
|
||||
require(asmValueParameters.size == valueParameters.size)
|
||||
|
||||
val args = valueParameters.zip(asmValueParameters)
|
||||
val variableFinder = VariableFinder(context)
|
||||
|
||||
return args.map { (parameter, asmType) ->
|
||||
val result = variableFinder.find(parameter, asmType)
|
||||
|
||||
if (result == null) {
|
||||
val name = parameter.debugString
|
||||
|
||||
fun isInsideDefaultInterfaceMethod(): Boolean {
|
||||
val method = context.frameProxy.safeLocation()?.safeMethod() ?: return false
|
||||
val desc = method.signature()
|
||||
return method.name().endsWith("\$default") && DEFAULT_METHOD_MARKERS.any { desc.contains("I${it.descriptor})") }
|
||||
}
|
||||
|
||||
if (parameter in compiledData.crossingBounds) {
|
||||
evaluationException("'$name' is not captured")
|
||||
} else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) {
|
||||
evaluationException("Cannot find the backing field '${parameter.name}'")
|
||||
} else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) {
|
||||
evaluationException("Parameter evaluation is not supported for '\$default' methods")
|
||||
} else {
|
||||
throw VariableFinder.variableNotFound(context, buildString {
|
||||
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result.value
|
||||
}
|
||||
}
|
||||
|
||||
override fun getModifier() = null
|
||||
|
||||
companion object {
|
||||
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> =
|
||||
Errors.INVISIBLE_REFERENCE_DIAGNOSTICS + setOf(Errors.EXPERIMENTAL_API_USAGE_ERROR)
|
||||
|
||||
private val DEFAULT_METHOD_MARKERS = listOf(AsmTypes.OBJECT_TYPE, AsmTypes.DEFAULT_CONSTRUCTOR_MARKER)
|
||||
|
||||
private fun InterpreterResult.toJdiValue(context: ExecutionContext): Value? {
|
||||
val jdiValue = when (this) {
|
||||
is ValueReturned -> result
|
||||
is ExceptionThrown -> {
|
||||
when {
|
||||
this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE ->
|
||||
evaluationException(InvocationException(this.exception.value as ObjectReference))
|
||||
this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE ->
|
||||
throw exception.value as Throwable
|
||||
else ->
|
||||
evaluationException(exception.toString())
|
||||
}
|
||||
}
|
||||
is AbnormalTermination -> evaluationException(message)
|
||||
else -> throw IllegalStateException("Unknown result value produced by eval4j")
|
||||
}
|
||||
|
||||
val sharedVar = if ((jdiValue is AbstractValue<*>)) getValueIfSharedVar(jdiValue, context) else null
|
||||
return sharedVar?.value ?: jdiValue.asJdiValue(context.vm.virtualMachine, jdiValue.asmType)
|
||||
}
|
||||
|
||||
private fun getValueIfSharedVar(value: Eval4JValue, context: ExecutionContext): VariableFinder.Result? {
|
||||
val obj = value.obj(value.asmType) as? ObjectReference ?: return null
|
||||
return VariableFinder.Result(EvaluatorValueConverter(context).unref(obj))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
|
||||
val allRequests = eventRequestManager().breakpointRequests() + eventRequestManager().classPrepareRequests()
|
||||
|
||||
try {
|
||||
allRequests.forEach { it.disable() }
|
||||
return block()
|
||||
} finally {
|
||||
allRequests.forEach { it.enable() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSpecialException(th: Throwable): Boolean {
|
||||
return when (th) {
|
||||
is ClassNotPreparedException,
|
||||
is InternalException,
|
||||
is AbsentInformationException,
|
||||
is ClassNotLoadedException,
|
||||
is IncompatibleThreadStateException,
|
||||
is InconsistentDebugInfoException,
|
||||
is ObjectCollectedException,
|
||||
is VMDisconnectedException -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportError(codeFragment: KtCodeFragment, position: SourcePosition?, message: String, throwable: Throwable? = null) {
|
||||
runReadAction {
|
||||
val contextFile = codeFragment.context?.containingFile
|
||||
|
||||
val attachments = arrayOf(
|
||||
attachmentByPsiFile(contextFile),
|
||||
attachmentByPsiFile(codeFragment),
|
||||
Attachment("breakpoint.info", "Position: " + position?.run { "${file.name}:$line" }),
|
||||
Attachment("context.info", runReadAction { codeFragment.context?.text ?: "null" })
|
||||
)
|
||||
|
||||
LOG.error(
|
||||
"Cannot evaluate a code fragment of type " + codeFragment::class.java + ": " + message.decapitalize(),
|
||||
throwable,
|
||||
mergeAttachments(*attachments)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createCompiledDataDescriptor(result: CodeFragmentCompiler.CompilationResult, sourcePosition: SourcePosition?): CompiledDataDescriptor {
|
||||
val localFunctionSuffixes = result.localFunctionSuffixes
|
||||
|
||||
val dumbParameters = ArrayList<CodeFragmentParameter.Dumb>(result.parameterInfo.parameters.size)
|
||||
for (parameter in result.parameterInfo.parameters) {
|
||||
val dumb = parameter.dumb
|
||||
if (dumb.kind == CodeFragmentParameter.Kind.LOCAL_FUNCTION) {
|
||||
val suffix = localFunctionSuffixes[dumb]
|
||||
if (suffix != null) {
|
||||
dumbParameters += dumb.copy(name = dumb.name + suffix)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
dumbParameters += dumb
|
||||
}
|
||||
|
||||
return CompiledDataDescriptor(
|
||||
result.classes,
|
||||
dumbParameters,
|
||||
result.parameterInfo.crossingBounds,
|
||||
result.mainMethodSignature,
|
||||
sourcePosition
|
||||
)
|
||||
}
|
||||
|
||||
private fun evaluationException(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg)
|
||||
private fun evaluationException(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e)
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerInvocationUtil
|
||||
import com.intellij.debugger.engine.ContextUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.ui.EditorEvaluationCommand
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.sun.jdi.ClassType
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.idea.debugger.getClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
abstract class KotlinRuntimeTypeEvaluator(
|
||||
editor: Editor?,
|
||||
expression: KtExpression,
|
||||
context: DebuggerContextImpl,
|
||||
indicator: ProgressIndicator
|
||||
) : EditorEvaluationCommand<KotlinType>(editor, expression, context, indicator) {
|
||||
|
||||
override fun threadAction() {
|
||||
var type: KotlinType? = null
|
||||
try {
|
||||
type = evaluate()
|
||||
} catch (ignored: ProcessCanceledException) {
|
||||
} catch (ignored: EvaluateException) {
|
||||
} finally {
|
||||
typeCalculationFinished(type)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun typeCalculationFinished(type: KotlinType?)
|
||||
|
||||
override fun evaluate(evaluationContext: EvaluationContextImpl): KotlinType? {
|
||||
val project = evaluationContext.project
|
||||
|
||||
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project) {
|
||||
val codeFragment = KtPsiFactory(myElement.project).createExpressionCodeFragment(
|
||||
myElement.text, myElement.containingFile.context
|
||||
)
|
||||
KotlinEvaluatorBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
|
||||
}
|
||||
|
||||
val value = evaluator.evaluate(evaluationContext)
|
||||
if (value != null) {
|
||||
return runReadAction { getCastableRuntimeType(evaluationContext.debugProcess.searchScope, value) }
|
||||
}
|
||||
|
||||
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.surrounded.expression.null"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getCastableRuntimeType(scope: GlobalSearchScope, value: Value): KotlinType? {
|
||||
val myValue = value.asValue()
|
||||
var psiClass = myValue.asmType.getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
|
||||
val type = value.type()
|
||||
if (type is ClassType) {
|
||||
val superclass = type.superclass()
|
||||
if (superclass != null && CommonClassNames.JAVA_LANG_OBJECT != superclass.name()) {
|
||||
psiClass = AsmType.getType(superclass.signature()).getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
}
|
||||
|
||||
for (interfaceType in type.interfaces()) {
|
||||
psiClass = AsmType.getType(interfaceType.signature()).getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
abstract class AbstractAndroidClassLoadingAdapter : ClassLoadingAdapter {
|
||||
protected fun dex(context: ExecutionContext, classes: Collection<ClassToLoad>): ByteArray? {
|
||||
return AndroidDexer.getInstances(context.project).single().dex(classes)
|
||||
}
|
||||
|
||||
protected fun wrapToByteBuffer(bytes: ArrayReference, context: ExecutionContext): ObjectReference {
|
||||
val classLoader = context.classLoader
|
||||
val byteBufferClass = context.findClass("java.nio.ByteBuffer", classLoader) as ClassType
|
||||
val wrapMethod = byteBufferClass.concreteMethodByName("wrap", "([B)Ljava/nio/ByteBuffer;")
|
||||
?: error("'wrap' method not found")
|
||||
|
||||
return context.invokeMethod(byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference
|
||||
}
|
||||
|
||||
protected fun tryLoadClass(context: ExecutionContext, fqName: String, classLoader: ClassLoaderReference?): ReferenceType? {
|
||||
return try {
|
||||
context.debugProcess.loadClass(context.evaluationContext, fqName, classLoader)
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
|
||||
|
||||
interface AndroidDexer {
|
||||
companion object : ProjectExtensionDescriptor<AndroidDexer>(
|
||||
"org.jetbrains.kotlin.androidDexer", AndroidDexer::class.java
|
||||
)
|
||||
|
||||
fun dex(classes: Collection<ClassToLoad>): ByteArray?
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.intellij.debugger.engine.JVMNameUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
|
||||
class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
|
||||
override fun isApplicable(context: ExecutionContext, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator) = with(info) {
|
||||
isCompilingEvaluatorPreferred && context.debugProcess.isDexDebug()
|
||||
}
|
||||
|
||||
private fun resolveClassLoaderClass(context: ExecutionContext): ClassType? {
|
||||
return try {
|
||||
val classLoader = context.classLoader
|
||||
tryLoadClass(context, "dalvik.system.InMemoryDexClassLoader", classLoader) as? ClassType
|
||||
} catch (e: EvaluateException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
|
||||
val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
|
||||
JVMNameUtil.CONSTRUCTOR_NAME, "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"
|
||||
) ?: error("Constructor method not found")
|
||||
|
||||
val dexBytes = dex(context, classes) ?: error("Can't dex classes")
|
||||
val dexBytesMirror = mirrorOfByteArray(dexBytes, context)
|
||||
val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context)
|
||||
|
||||
val classLoader = context.classLoader
|
||||
val args = listOf(dexByteBuffer, classLoader)
|
||||
val newClassLoader = context.newInstance(inMemoryClassLoaderClass, constructorMethod, args) as ClassLoaderReference
|
||||
context.keepReference(newClassLoader)
|
||||
|
||||
return newClassLoader
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.sun.jdi.ArrayReference
|
||||
import com.sun.jdi.ArrayType
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
import kotlin.math.min
|
||||
|
||||
interface ClassLoadingAdapter {
|
||||
companion object {
|
||||
private const val CHUNK_SIZE = 4096
|
||||
|
||||
private val ADAPTERS = listOf(
|
||||
AndroidOClassLoadingAdapter(),
|
||||
OrdinaryClassLoadingAdapter()
|
||||
)
|
||||
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
val mainClass = classes.firstOrNull { it.isMainClass } ?: return null
|
||||
|
||||
var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1)
|
||||
if (!info.containsAdditionalClasses) {
|
||||
info = analyzeClass(mainClass, info)
|
||||
}
|
||||
|
||||
for (adapter in ADAPTERS) {
|
||||
if (adapter.isApplicable(context, info)) {
|
||||
return adapter.loadClasses(context, classes)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
data class ClassInfoForEvaluator(
|
||||
val containsLoops: Boolean = false,
|
||||
val containsCodeUnsupportedInEval4J: Boolean = false,
|
||||
val containsAdditionalClasses: Boolean = false
|
||||
) {
|
||||
val isCompilingEvaluatorPreferred: Boolean
|
||||
get() = containsLoops || containsCodeUnsupportedInEval4J || containsAdditionalClasses
|
||||
}
|
||||
|
||||
private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
|
||||
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, 0) }
|
||||
val methodToRun = classNode.methods.single { it.name == GENERATED_FUNCTION_NAME }
|
||||
|
||||
val visitedLabels = hashSetOf<Label>()
|
||||
|
||||
tailrec fun analyzeInsn(insn: AbstractInsnNode, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
|
||||
when (insn) {
|
||||
is LabelNode -> visitedLabels += insn.label
|
||||
is JumpInsnNode -> {
|
||||
if (insn.label.label in visitedLabels) {
|
||||
return info.copy(containsLoops = true)
|
||||
}
|
||||
}
|
||||
is TableSwitchInsnNode, is LookupSwitchInsnNode -> {
|
||||
return info.copy(containsCodeUnsupportedInEval4J = true)
|
||||
}
|
||||
}
|
||||
|
||||
val nextInsn = insn.next ?: return info
|
||||
return analyzeInsn(nextInsn, info)
|
||||
}
|
||||
|
||||
val firstInsn = methodToRun.instructions?.first ?: return info
|
||||
return analyzeInsn(firstInsn, info)
|
||||
}
|
||||
}
|
||||
|
||||
fun isApplicable(context: ExecutionContext, info: ClassInfoForEvaluator): Boolean
|
||||
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference
|
||||
|
||||
fun mirrorOfByteArray(bytes: ByteArray, context: ExecutionContext): ArrayReference {
|
||||
val classLoader = context.classLoader
|
||||
val arrayClass = context.findClass("byte[]", classLoader) as ArrayType
|
||||
val reference = context.newInstance(arrayClass, bytes.size)
|
||||
context.keepReference(reference)
|
||||
|
||||
val mirrors = ArrayList<Value>(bytes.size)
|
||||
for (byte in bytes) {
|
||||
mirrors += context.vm.mirrorOf(byte)
|
||||
}
|
||||
|
||||
var loaded = 0
|
||||
while (loaded < mirrors.size) {
|
||||
val chunkSize = min(CHUNK_SIZE, mirrors.size - loaded)
|
||||
reference.setValues(loaded, mirrors, loaded, chunkSize)
|
||||
loaded += chunkSize
|
||||
}
|
||||
|
||||
return reference
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.impl.ClassLoadingUtils
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import com.sun.jdi.ClassType
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
|
||||
class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
private companion object {
|
||||
// This list should contain all superclasses of lambda classes.
|
||||
// The order is relevant here: if we load Lambda first instead, during the definition of Lambda the class loader will try
|
||||
// to load its superclass. It will succeed, probably with the help of some parent class loader, and the subsequent attempt to define
|
||||
// the patched version of that superclass will fail with LinkageError (cannot redefine class)
|
||||
private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda"))
|
||||
|
||||
// Copied from com.intellij.debugger.ui.impl.watch.CompilingEvaluator.changeSuperToMagicAccessor
|
||||
fun changeSuperToMagicAccessor(bytes: ByteArray): ByteArray {
|
||||
val classWriter = ClassWriter(0)
|
||||
val classVisitor = object : ClassVisitor(Opcodes.API_VERSION, classWriter) {
|
||||
override fun visit(
|
||||
version: Int,
|
||||
access: Int,
|
||||
name: String,
|
||||
signature: String?,
|
||||
superName: String?,
|
||||
interfaces: Array<String>?
|
||||
) {
|
||||
var newSuperName = superName
|
||||
if ("java/lang/Object" == newSuperName) {
|
||||
newSuperName = "sun/reflect/MagicAccessorImpl"
|
||||
}
|
||||
|
||||
super.visit(version, access, name, signature, newSuperName, interfaces)
|
||||
}
|
||||
}
|
||||
ClassReader(bytes).accept(classVisitor, 0)
|
||||
return classWriter.toByteArray()
|
||||
}
|
||||
|
||||
fun useMagicAccessor(context: ExecutionContext): Boolean {
|
||||
val rawVersion = context.vm.version()?.substringBefore('_') ?: return false
|
||||
val javaVersion = JavaSdkVersion.fromVersionString(rawVersion) ?: return false
|
||||
return !javaVersion.isAtLeast(JavaSdkVersion.JDK_1_9)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicable(context: ExecutionContext, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator): Boolean {
|
||||
return info.isCompilingEvaluatorPreferred && context.classLoader != null && !context.debugProcess.isDexDebug()
|
||||
}
|
||||
|
||||
override fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
val process = context.debugProcess
|
||||
|
||||
val classLoader = try {
|
||||
ClassLoadingUtils.getClassLoader(context.evaluationContext, process)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error creating evaluation class loader: $e", e)
|
||||
}
|
||||
|
||||
try {
|
||||
defineClasses(classes, context, classLoader)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error during classes definition $e", e)
|
||||
}
|
||||
|
||||
return classLoader
|
||||
}
|
||||
|
||||
private fun defineClasses(
|
||||
classes: Collection<ClassToLoad>,
|
||||
context: ExecutionContext,
|
||||
classLoader: ClassLoaderReference
|
||||
) {
|
||||
val classesToLoad = if (classes.size == 1) {
|
||||
// No need in loading lambda superclass if there're no lambdas
|
||||
classes
|
||||
} else {
|
||||
val lambdaSuperclasses = LAMBDA_SUPERCLASSES.map {
|
||||
ClassToLoad(it.name, it.name.replace('.', '/') + ".class", it.bytes)
|
||||
}
|
||||
lambdaSuperclasses + classes
|
||||
}
|
||||
|
||||
for ((className, _, bytes) in classesToLoad) {
|
||||
val patchedBytes = if (useMagicAccessor(context)) changeSuperToMagicAccessor(bytes) else bytes
|
||||
defineClass(className, patchedBytes, context, classLoader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun defineClass(
|
||||
name: String,
|
||||
bytes: ByteArray,
|
||||
context: ExecutionContext,
|
||||
classLoader: ClassLoaderReference
|
||||
) {
|
||||
try {
|
||||
val vm = context.vm
|
||||
val classLoaderType = classLoader.referenceType() as ClassType
|
||||
val defineMethod = classLoaderType.concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;")
|
||||
val nameObj = vm.mirrorOf(name)
|
||||
|
||||
val args = listOf(nameObj, mirrorOfByteArray(bytes, context), vm.mirrorOf(0), vm.mirrorOf(bytes.size))
|
||||
context.invokeMethod(classLoader, defineMethod, args)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error during class $name definition: $e", e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ClassBytes(val name: String) {
|
||||
val bytes: ByteArray by lazy {
|
||||
val inputStream = this::class.java.classLoader.getResourceAsStream(name.replace('.', '/') + ".class")
|
||||
?: throw EvaluateException("Couldn't find $name class in current class loader")
|
||||
|
||||
inputStream.use {
|
||||
it.readBytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.output.OutputFile
|
||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.codegen.CodeFragmentCodegen.Companion.getSharedTypeIfApplicable
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension.Context as InCo
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_CLASS_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_FUNCTION_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CompiledDataDescriptor.MethodSignature
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.lazy.data.KtClassOrObjectInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
|
||||
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
|
||||
import org.jetbrains.kotlin.resolve.scopes.ChainedMemberScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
|
||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.SimpleType
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class CodeFragmentCompiler(private val executionContext: ExecutionContext) {
|
||||
data class CompilationResult(
|
||||
val classes: List<ClassToLoad>,
|
||||
val parameterInfo: CodeFragmentParameterInfo,
|
||||
val localFunctionSuffixes: Map<CodeFragmentParameter.Dumb, String>,
|
||||
val mainMethodSignature: MethodSignature
|
||||
)
|
||||
|
||||
fun compile(codeFragment: KtCodeFragment, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): CompilationResult {
|
||||
return runReadAction { doCompile(codeFragment, bindingContext, moduleDescriptor) }
|
||||
}
|
||||
|
||||
private fun doCompile(
|
||||
codeFragment: KtCodeFragment, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor
|
||||
): CompilationResult {
|
||||
require(codeFragment is KtBlockCodeFragment || codeFragment is KtExpressionCodeFragment) {
|
||||
"Unsupported code fragment type: $codeFragment"
|
||||
}
|
||||
|
||||
val project = codeFragment.project
|
||||
val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacade(listOf(codeFragment))
|
||||
val resolveSession = resolutionFacade.getFrontendService(ResolveSession::class.java)
|
||||
val moduleDescriptorWrapper = EvaluatorModuleDescriptor(codeFragment, moduleDescriptor, resolveSession)
|
||||
|
||||
val defaultReturnType = moduleDescriptor.builtIns.unitType
|
||||
val returnType = getReturnType(codeFragment, bindingContext, defaultReturnType)
|
||||
|
||||
val compilerConfiguration = CompilerConfiguration()
|
||||
compilerConfiguration.languageVersionSettings = codeFragment.languageVersionSettings
|
||||
|
||||
val generationState = GenerationState.Builder(
|
||||
project, ClassBuilderFactories.BINARIES, moduleDescriptorWrapper,
|
||||
bindingContext, listOf(codeFragment), compilerConfiguration
|
||||
).build()
|
||||
|
||||
val parameterInfo = CodeFragmentParameterAnalyzer(executionContext, codeFragment, bindingContext).analyze()
|
||||
val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment(
|
||||
codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME),
|
||||
parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator
|
||||
)
|
||||
|
||||
val codegenInfo = CodeFragmentCodegenInfo(classDescriptor, methodDescriptor, parameterInfo.parameters)
|
||||
CodeFragmentCodegen.setCodeFragmentInfo(codeFragment, codegenInfo)
|
||||
|
||||
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
|
||||
|
||||
val classes = generationState.factory.asList().filterClassFiles()
|
||||
.map { ClassToLoad(it.internalClassName, it.relativePath, it.asByteArray()) }
|
||||
|
||||
val methodSignature = getMethodSignature(methodDescriptor, parameterInfo.parameters, generationState)
|
||||
val functionSuffixes = getLocalFunctionSuffixes(parameterInfo.parameters, generationState.typeMapper)
|
||||
|
||||
generationState.destroy()
|
||||
|
||||
return CompilationResult(classes, parameterInfo, functionSuffixes, methodSignature)
|
||||
}
|
||||
|
||||
private fun getLocalFunctionSuffixes(
|
||||
parameters: List<CodeFragmentParameter.Smart>,
|
||||
typeMapper: KotlinTypeMapper
|
||||
): Map<CodeFragmentParameter.Dumb, String> {
|
||||
val result = mutableMapOf<CodeFragmentParameter.Dumb, String>()
|
||||
|
||||
for (parameter in parameters) {
|
||||
if (parameter.kind != CodeFragmentParameter.Kind.LOCAL_FUNCTION) {
|
||||
continue
|
||||
}
|
||||
|
||||
val ownerClassName = typeMapper.mapOwner(parameter.targetDescriptor).internalName
|
||||
val lastDollarIndex = ownerClassName.lastIndexOf('$').takeIf { it >= 0 } ?: continue
|
||||
result[parameter.dumb] = ownerClassName.drop(lastDollarIndex)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getMethodSignature(
|
||||
methodDescriptor: FunctionDescriptor,
|
||||
parameters: List<CodeFragmentParameter.Smart>,
|
||||
state: GenerationState
|
||||
): MethodSignature {
|
||||
val typeMapper = state.typeMapper
|
||||
val asmSignature = typeMapper.mapSignatureSkipGeneric(methodDescriptor)
|
||||
val asmParameters = parameters.zip(asmSignature.valueParameters).map { (param, sigParam) ->
|
||||
getSharedTypeIfApplicable(param.targetDescriptor, typeMapper) ?: sigParam.asmType
|
||||
}
|
||||
|
||||
return MethodSignature(asmParameters, asmSignature.returnType)
|
||||
}
|
||||
|
||||
private fun getReturnType(
|
||||
codeFragment: KtCodeFragment,
|
||||
bindingContext: BindingContext,
|
||||
defaultReturnType: SimpleType
|
||||
): KotlinType {
|
||||
return when (codeFragment) {
|
||||
is KtExpressionCodeFragment -> {
|
||||
val typeInfo = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, codeFragment.getContentElement()]
|
||||
typeInfo?.type ?: defaultReturnType
|
||||
}
|
||||
is KtBlockCodeFragment -> {
|
||||
val blockExpression = codeFragment.getContentElement()
|
||||
val lastStatement = blockExpression.statements.lastOrNull() ?: return defaultReturnType
|
||||
val typeInfo = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, lastStatement]
|
||||
typeInfo?.type ?: defaultReturnType
|
||||
}
|
||||
else -> defaultReturnType
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDescriptorsForCodeFragment(
|
||||
declaration: KtCodeFragment,
|
||||
className: Name,
|
||||
methodName: Name,
|
||||
parameterInfo: CodeFragmentParameterInfo,
|
||||
returnType: KotlinType,
|
||||
packageFragmentDescriptor: PackageFragmentDescriptor
|
||||
): Pair<ClassDescriptor, FunctionDescriptor> {
|
||||
val classDescriptor = ClassDescriptorImpl(
|
||||
packageFragmentDescriptor, className, Modality.FINAL, ClassKind.OBJECT,
|
||||
emptyList(),
|
||||
KotlinSourceElement(declaration),
|
||||
false,
|
||||
LockBasedStorageManager.NO_LOCKS
|
||||
)
|
||||
|
||||
val methodDescriptor = SimpleFunctionDescriptorImpl.create(
|
||||
classDescriptor, Annotations.EMPTY, methodName,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED, classDescriptor.source
|
||||
)
|
||||
|
||||
val parameters = parameterInfo.parameters.mapIndexed { index, parameter ->
|
||||
ValueParameterDescriptorImpl(
|
||||
methodDescriptor, null, index, Annotations.EMPTY, Name.identifier("p$index"),
|
||||
parameter.targetType,
|
||||
declaresDefaultValue = false,
|
||||
isCrossinline = false,
|
||||
isNoinline = false,
|
||||
varargElementType = null,
|
||||
source = SourceElement.NO_SOURCE
|
||||
)
|
||||
}
|
||||
|
||||
methodDescriptor.initialize(
|
||||
null, classDescriptor.thisAsReceiverParameter, emptyList(),
|
||||
parameters, returnType, Modality.FINAL, Visibilities.PUBLIC
|
||||
)
|
||||
|
||||
val memberScope = EvaluatorMemberScopeForMethod(methodDescriptor)
|
||||
|
||||
val constructor = ClassConstructorDescriptorImpl.create(classDescriptor, Annotations.EMPTY, true, classDescriptor.source)
|
||||
classDescriptor.initialize(memberScope, setOf(constructor), constructor)
|
||||
|
||||
return Pair(classDescriptor, methodDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class EvaluatorMemberScopeForMethod(private val methodDescriptor: SimpleFunctionDescriptor) : MemberScopeImpl() {
|
||||
override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> {
|
||||
return if (name == methodDescriptor.name) {
|
||||
listOf(methodDescriptor)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getContributedDescriptors(
|
||||
kindFilter: DescriptorKindFilter,
|
||||
nameFilter: (Name) -> Boolean
|
||||
): Collection<DeclarationDescriptor> {
|
||||
return if (kindFilter.accepts(methodDescriptor) && nameFilter(methodDescriptor.name)) {
|
||||
listOf(methodDescriptor)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFunctionNames() = setOf(methodDescriptor.name)
|
||||
|
||||
override fun printScopeStructure(p: Printer) {
|
||||
p.println(this::class.java.simpleName)
|
||||
}
|
||||
}
|
||||
|
||||
private class EvaluatorModuleDescriptor(
|
||||
val codeFragment: KtCodeFragment,
|
||||
val moduleDescriptor: ModuleDescriptor,
|
||||
resolveSession: ResolveSession
|
||||
) : ModuleDescriptor by moduleDescriptor {
|
||||
private val declarationProvider = object : PackageMemberDeclarationProvider {
|
||||
override fun getPackageFiles() = listOf(codeFragment)
|
||||
override fun containsFile(file: KtFile) = file == codeFragment
|
||||
|
||||
override fun getDeclarationNames() = emptySet<Name>()
|
||||
override fun getDeclarations(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) = emptyList<KtDeclaration>()
|
||||
override fun getClassOrObjectDeclarations(name: Name) = emptyList<KtClassOrObjectInfo<*>>()
|
||||
override fun getAllDeclaredSubPackages(nameFilter: (Name) -> Boolean) = emptyList<FqName>()
|
||||
override fun getFunctionDeclarations(name: Name) = emptyList<KtNamedFunction>()
|
||||
override fun getPropertyDeclarations(name: Name) = emptyList<KtProperty>()
|
||||
override fun getTypeAliasDeclarations(name: Name) = emptyList<KtTypeAlias>()
|
||||
override fun getDestructuringDeclarationsEntries(name: Name) = emptyList<KtDestructuringDeclarationEntry>()
|
||||
override fun getScriptDeclarations(name: Name) = emptyList<KtScriptInfo>()
|
||||
}
|
||||
|
||||
val packageFragmentForEvaluator = LazyPackageDescriptor(this, FqName.ROOT, resolveSession, declarationProvider)
|
||||
|
||||
override fun getPackage(fqName: FqName): PackageViewDescriptor {
|
||||
val originalPackageDescriptor = moduleDescriptor.getPackage(fqName)
|
||||
if (fqName != FqName.ROOT) {
|
||||
return originalPackageDescriptor
|
||||
}
|
||||
|
||||
return object : DeclarationDescriptorImpl(Annotations.EMPTY, fqName.shortNameOrSpecial()), PackageViewDescriptor {
|
||||
override fun getContainingDeclaration() = originalPackageDescriptor.containingDeclaration
|
||||
|
||||
override val fqName get() = originalPackageDescriptor.fqName
|
||||
override val module get() = this@EvaluatorModuleDescriptor
|
||||
|
||||
override val memberScope by lazy {
|
||||
if (fragments.isEmpty()) {
|
||||
MemberScope.Empty
|
||||
} else {
|
||||
val scopes = fragments.map { it.getMemberScope() } + SubpackagesScope(module, fqName)
|
||||
ChainedMemberScope("package view scope for $fqName in ${module.name}", scopes)
|
||||
}
|
||||
}
|
||||
|
||||
override val fragments by lazy { originalPackageDescriptor.fragments + packageFragmentForEvaluator }
|
||||
|
||||
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R {
|
||||
return visitor.visitPackageViewDescriptor(this, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val OutputFile.internalClassName: String
|
||||
get() = relativePath.removeSuffix(".class").replace('/', '.')
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
|
||||
import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class CodeFragmentParameterInfo(
|
||||
val parameters: List<Smart>,
|
||||
val crossingBounds: Set<Dumb>
|
||||
)
|
||||
|
||||
/*
|
||||
The purpose of this class is to figure out what parameters the received code fragment captures.
|
||||
It handles both directly mentioned names such as local variables or parameters and implicit values (dispatch/extension receivers).
|
||||
*/
|
||||
class CodeFragmentParameterAnalyzer(
|
||||
private val context: ExecutionContext,
|
||||
private val codeFragment: KtCodeFragment,
|
||||
private val bindingContext: BindingContext
|
||||
) {
|
||||
private val parameters = LinkedHashMap<DeclarationDescriptor, Smart>()
|
||||
private val crossingBounds = mutableSetOf<Dumb>()
|
||||
|
||||
private val onceUsedChecker = OnceUsedChecker(CodeFragmentParameterAnalyzer::class.java)
|
||||
|
||||
private val containingPrimaryConstructor: ConstructorDescriptor? by lazy {
|
||||
context.frameProxy.safeLocation()?.safeMethod()?.takeIf { it.isConstructor } ?: return@lazy null
|
||||
val constructor = codeFragment.context?.getParentOfType<KtPrimaryConstructor>(false) ?: return@lazy null
|
||||
bindingContext[BindingContext.CONSTRUCTOR, constructor]
|
||||
}
|
||||
|
||||
fun analyze(): CodeFragmentParameterInfo {
|
||||
onceUsedChecker.trigger()
|
||||
|
||||
codeFragment.accept(object : KtTreeVisitor<Unit>() {
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression, data: Unit?): Void? {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return null
|
||||
processResolvedCall(resolvedCall, expression)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun processResolvedCall(resolvedCall: ResolvedCall<*>, expression: KtSimpleNameExpression) {
|
||||
// Capture dispatch receiver for the extension callable
|
||||
run {
|
||||
val descriptor = resolvedCall.resultingDescriptor
|
||||
val containingClass = descriptor?.containingDeclaration as? ClassDescriptor
|
||||
val extensionParameter = descriptor?.extensionReceiverParameter
|
||||
if (descriptor != null && descriptor !is DebuggerFieldPropertyDescriptor
|
||||
&& extensionParameter != null && containingClass != null
|
||||
) {
|
||||
if (containingClass.kind != ClassKind.OBJECT) {
|
||||
processDispatchReceiver(containingClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runReadAction { expression.isDotSelector() }) {
|
||||
// The receiver expression is already captured for this reference
|
||||
return
|
||||
}
|
||||
|
||||
if (isCodeFragmentDeclaration(resolvedCall.resultingDescriptor)) {
|
||||
// The reference is from the code fragment we analyze, no need to capture
|
||||
return
|
||||
}
|
||||
|
||||
var processed = false
|
||||
|
||||
val extensionReceiver = resolvedCall.extensionReceiver
|
||||
if (extensionReceiver is ImplicitReceiver) {
|
||||
val descriptor = extensionReceiver.declarationDescriptor
|
||||
val parameter = processReceiver(extensionReceiver)
|
||||
checkBounds(descriptor, expression, parameter)
|
||||
processed = true
|
||||
}
|
||||
|
||||
val dispatchReceiver = resolvedCall.dispatchReceiver
|
||||
if (dispatchReceiver is ImplicitReceiver) {
|
||||
val descriptor = dispatchReceiver.declarationDescriptor
|
||||
val parameter = processReceiver(dispatchReceiver)
|
||||
if (parameter != null) {
|
||||
checkBounds(descriptor, expression, parameter)
|
||||
processed = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!processed && resolvedCall.resultingDescriptor is SyntheticFieldDescriptor) {
|
||||
val descriptor = resolvedCall.resultingDescriptor as SyntheticFieldDescriptor
|
||||
val parameter = processSyntheticFieldVariable(descriptor)
|
||||
if (parameter != null) {
|
||||
checkBounds(descriptor, expression, parameter)
|
||||
processed = true
|
||||
}
|
||||
}
|
||||
|
||||
// If a reference has receivers, we can calculate its value using them, no need to capture
|
||||
if (!processed) {
|
||||
if (resolvedCall is VariableAsFunctionResolvedCall) {
|
||||
processResolvedCall(resolvedCall.functionCall, expression)
|
||||
processResolvedCall(resolvedCall.variableCall, expression)
|
||||
} else {
|
||||
processDescriptor(resolvedCall.resultingDescriptor, expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processDescriptor(descriptor: DeclarationDescriptor, expression: KtSimpleNameExpression) {
|
||||
val parameter = processDebugLabel(descriptor)
|
||||
?: processCoroutineContextCall(descriptor)
|
||||
?: processSimpleNameExpression(descriptor)
|
||||
checkBounds(descriptor, expression, parameter)
|
||||
}
|
||||
|
||||
override fun visitThisExpression(expression: KtThisExpression, data: Unit?): Void? {
|
||||
val instanceReference = runReadAction { expression.instanceReference }
|
||||
val target = bindingContext[BindingContext.REFERENCE_TARGET, instanceReference]
|
||||
|
||||
if (isCodeFragmentDeclaration(target)) {
|
||||
// The reference is from the code fragment we analyze, no need to capture
|
||||
return null
|
||||
}
|
||||
|
||||
val parameter = when (target) {
|
||||
is ClassDescriptor -> processDispatchReceiver(target)
|
||||
is CallableDescriptor -> {
|
||||
val type = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, expression]?.type
|
||||
type?.let { processExtensionReceiver(target, type, expression.getLabelName()) }
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (parameter != null) {
|
||||
checkBounds(target, expression, parameter)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun visitSuperExpression(expression: KtSuperExpression, data: Unit?): Void {
|
||||
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'super' call expression is not supported")
|
||||
}
|
||||
}, Unit)
|
||||
|
||||
return CodeFragmentParameterInfo(parameters.values.toList(), crossingBounds)
|
||||
}
|
||||
|
||||
private fun processReceiver(receiver: ImplicitReceiver): Smart? {
|
||||
return when (receiver) {
|
||||
is ImplicitClassReceiver -> processDispatchReceiver(receiver.classDescriptor)
|
||||
is ExtensionReceiver -> processExtensionReceiver(receiver.declarationDescriptor, receiver.type, null)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun processDispatchReceiver(descriptor: ClassDescriptor): Smart? {
|
||||
if (descriptor.kind == ClassKind.OBJECT || containingPrimaryConstructor != null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = descriptor.defaultType
|
||||
return parameters.getOrPut(descriptor) {
|
||||
Smart(Dumb(Kind.DISPATCH_RECEIVER, "", AsmUtil.THIS + "@" + descriptor.name.asString()), type, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processExtensionReceiver(descriptor: CallableDescriptor, receiverType: KotlinType, label: String?): Smart? {
|
||||
if (isFakeFunctionForJavaContext(descriptor)) {
|
||||
return processFakeJavaCodeReceiver(descriptor)
|
||||
}
|
||||
|
||||
val actualLabel = label ?: getLabel(descriptor) ?: return null
|
||||
val receiverParameter = descriptor.extensionReceiverParameter ?: return null
|
||||
|
||||
return parameters.getOrPut(descriptor) {
|
||||
Smart(Dumb(Kind.EXTENSION_RECEIVER, actualLabel, AsmUtil.THIS + "@" + actualLabel), receiverType, receiverParameter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLabel(callableDescriptor: CallableDescriptor): String? {
|
||||
val source = callableDescriptor.source.getPsi()
|
||||
|
||||
if (source is KtFunctionLiteral) {
|
||||
getCallLabelForLambdaArgument(source, bindingContext)?.let { return it }
|
||||
}
|
||||
|
||||
return callableDescriptor.name.takeIf { !it.isSpecial }?.asString()
|
||||
}
|
||||
|
||||
private fun isFakeFunctionForJavaContext(descriptor: CallableDescriptor): Boolean {
|
||||
return descriptor is FunctionDescriptor
|
||||
&& descriptor.name.asString() == FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||
&& codeFragment.getCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE) != null
|
||||
}
|
||||
|
||||
private fun processFakeJavaCodeReceiver(descriptor: CallableDescriptor): Smart? {
|
||||
val receiverParameter = descriptor
|
||||
.takeIf { descriptor is FunctionDescriptor }
|
||||
?.extensionReceiverParameter
|
||||
?: return null
|
||||
|
||||
val label = FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||
val type = receiverParameter.type
|
||||
return parameters.getOrPut(descriptor) {
|
||||
Smart(Dumb(Kind.FAKE_JAVA_OUTER_CLASS, label, AsmUtil.THIS), type, receiverParameter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processSyntheticFieldVariable(descriptor: SyntheticFieldDescriptor): Smart? {
|
||||
val propertyDescriptor = descriptor.propertyDescriptor
|
||||
val fieldName = propertyDescriptor.name.asString()
|
||||
val type = propertyDescriptor.type
|
||||
return parameters.getOrPut(descriptor) {
|
||||
Smart(Dumb(Kind.FIELD_VAR, fieldName, "field"), type, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processSimpleNameExpression(target: DeclarationDescriptor): Smart? {
|
||||
if (target is ValueParameterDescriptor && target.isCrossinline) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'crossinline' lambdas is not supported")
|
||||
}
|
||||
|
||||
val isLocalTarget = (target as? DeclarationDescriptorWithVisibility)?.visibility == Visibilities.LOCAL
|
||||
|
||||
val isPrimaryConstructorParameter = !isLocalTarget
|
||||
&& target is PropertyDescriptor
|
||||
&& isContainingPrimaryConstructorParameter(target)
|
||||
|
||||
if (!isLocalTarget && !isPrimaryConstructorParameter) {
|
||||
return null
|
||||
}
|
||||
|
||||
return when (target) {
|
||||
is FunctionDescriptor -> {
|
||||
val type = SingleAbstractMethodUtils.getFunctionTypeForAbstractMethod(target, false)
|
||||
parameters.getOrPut(target) {
|
||||
Smart(Dumb(Kind.LOCAL_FUNCTION, target.name.asString()), type, target)
|
||||
}
|
||||
}
|
||||
is ValueDescriptor -> {
|
||||
parameters.getOrPut(target) {
|
||||
val type = target.type
|
||||
@Suppress("DEPRECATION")
|
||||
val kind = if (target is LocalVariableDescriptor && target.isDelegated) Kind.DELEGATED else Kind.ORDINARY
|
||||
Smart(Dumb(kind, target.name.asString()), type, target)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isContainingPrimaryConstructorParameter(target: PropertyDescriptor): Boolean {
|
||||
val primaryConstructor = containingPrimaryConstructor ?: return false
|
||||
for (parameter in primaryConstructor.valueParameters) {
|
||||
val property = bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, parameter]
|
||||
if (target == property) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun processCoroutineContextCall(target: DeclarationDescriptor): Smart? {
|
||||
if (target is PropertyDescriptor && target.fqNameSafe == COROUTINE_CONTEXT_1_3_FQ_NAME) {
|
||||
return parameters.getOrPut(target) {
|
||||
Smart(Dumb(Kind.COROUTINE_CONTEXT, ""), target.type, target)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun processDebugLabel(target: DeclarationDescriptor): Smart? {
|
||||
val debugLabelPropertyDescriptor = target as? DebugLabelPropertyDescriptor ?: return null
|
||||
val labelName = debugLabelPropertyDescriptor.labelName
|
||||
val debugString = debugLabelPropertyDescriptor.name.asString()
|
||||
|
||||
return parameters.getOrPut(target) {
|
||||
val type = debugLabelPropertyDescriptor.type
|
||||
Smart(Dumb(Kind.DEBUG_LABEL, labelName, debugString), type, debugLabelPropertyDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkBounds(descriptor: DeclarationDescriptor?, expression: KtExpression, parameter: Smart?) {
|
||||
if (parameter == null || descriptor !is DeclarationDescriptorWithSource) {
|
||||
return
|
||||
}
|
||||
|
||||
val targetPsi = descriptor.source.getPsi()
|
||||
if (targetPsi != null && doesCrossInlineBounds(expression, targetPsi)) {
|
||||
crossingBounds += parameter.dumb
|
||||
}
|
||||
}
|
||||
|
||||
private fun doesCrossInlineBounds(expression: PsiElement, declaration: PsiElement): Boolean {
|
||||
val declarationParent = declaration.parent ?: return false
|
||||
var currentParent: PsiElement? = expression.parent?.takeIf { it.isInside(declarationParent) } ?: return false
|
||||
|
||||
while (currentParent != null && currentParent != declarationParent) {
|
||||
if (currentParent is KtFunction) {
|
||||
val functionDescriptor = bindingContext[BindingContext.FUNCTION, currentParent]
|
||||
if (functionDescriptor != null && !functionDescriptor.isInline) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
currentParent = when (currentParent) {
|
||||
is KtCodeFragment -> currentParent.context
|
||||
else -> currentParent.parent
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isCodeFragmentDeclaration(descriptor: DeclarationDescriptor?): Boolean {
|
||||
if (descriptor is ValueParameterDescriptor && isCodeFragmentDeclaration(descriptor.containingDeclaration)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (descriptor !is DeclarationDescriptorWithSource) {
|
||||
return false
|
||||
}
|
||||
|
||||
return descriptor.source.getPsi()?.containingFile is KtCodeFragment
|
||||
}
|
||||
|
||||
private tailrec fun PsiElement.isInside(parent: PsiElement): Boolean {
|
||||
if (parent.isAncestor(this)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val context = (this.containingFile as? KtCodeFragment)?.context ?: return false
|
||||
return context.isInside(parent)
|
||||
}
|
||||
}
|
||||
|
||||
private class OnceUsedChecker(private val clazz: Class<*>) {
|
||||
private var used = false
|
||||
|
||||
fun trigger() {
|
||||
if (used) {
|
||||
error(clazz.name + " may be only used once")
|
||||
}
|
||||
|
||||
used = true
|
||||
}
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
|
||||
import org.jetbrains.kotlin.builtins.PrimitiveType
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.backend.common.SimpleMemberScope
|
||||
import com.sun.jdi.Type as JdiType
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.getClassDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.externalDescriptors
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
class DebugLabelPropertyDescriptorProvider(val codeFragment: KtCodeFragment, val debugProcess: DebugProcessImpl) {
|
||||
companion object {
|
||||
fun getMarkupMap(debugProcess: DebugProcessImpl) = doGetMarkupMap(debugProcess) ?: emptyMap()
|
||||
|
||||
private fun doGetMarkupMap(debugProcess: DebugProcessImpl): Map<out Value?, ValueMarkup>? {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return NodeDescriptorImpl.getMarkupMap(debugProcess)
|
||||
}
|
||||
|
||||
val debugSession = debugProcess.session.xDebugSession as? XDebugSessionImpl
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return debugSession?.valueMarkers?.allMarkers?.filterKeys { it is Value? } as Map<out Value?, ValueMarkup>?
|
||||
}
|
||||
}
|
||||
|
||||
private val moduleDescriptor = DebugLabelModuleDescriptor
|
||||
|
||||
fun supplyDebugLabels() {
|
||||
val packageFragment = object : PackageFragmentDescriptorImpl(moduleDescriptor, FqName.ROOT) {
|
||||
val properties = createDebugLabelDescriptors(this)
|
||||
override fun getMemberScope() = SimpleMemberScope(properties)
|
||||
}
|
||||
|
||||
codeFragment.externalDescriptors = packageFragment.properties
|
||||
}
|
||||
|
||||
private fun createDebugLabelDescriptors(containingDeclaration: PackageFragmentDescriptor): List<PropertyDescriptor> {
|
||||
val markupMap = getMarkupMap(debugProcess)
|
||||
|
||||
val result = ArrayList<PropertyDescriptor>(markupMap.size)
|
||||
|
||||
nextValue@ for ((value, markup) in markupMap) {
|
||||
val labelName = markup.text
|
||||
val kotlinType = value?.type()?.let { convertType(it) } ?: moduleDescriptor.builtIns.nullableAnyType
|
||||
result += createDebugLabelDescriptor(labelName, kotlinType, containingDeclaration)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createDebugLabelDescriptor(
|
||||
labelName: String,
|
||||
type: KotlinType,
|
||||
containingDeclaration: PackageFragmentDescriptor
|
||||
): PropertyDescriptor {
|
||||
val propertyDescriptor = DebugLabelPropertyDescriptor(containingDeclaration, labelName)
|
||||
propertyDescriptor.setType(type, emptyList(), null, null)
|
||||
|
||||
val getterDescriptor = PropertyGetterDescriptorImpl(
|
||||
propertyDescriptor,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PUBLIC,
|
||||
/* isDefault = */ false,
|
||||
/* isExternal = */ false,
|
||||
/* isInline = */ false,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
/* original = */ null,
|
||||
SourceElement.NO_SOURCE
|
||||
).apply { initialize(type) }
|
||||
|
||||
propertyDescriptor.initialize(getterDescriptor, null)
|
||||
return propertyDescriptor
|
||||
}
|
||||
|
||||
private fun convertType(type: JdiType): KotlinType {
|
||||
val builtIns = moduleDescriptor.builtIns
|
||||
|
||||
return when (type) {
|
||||
is VoidType -> builtIns.unitType
|
||||
is LongType -> builtIns.longType
|
||||
is DoubleType -> builtIns.doubleType
|
||||
is CharType -> builtIns.charType
|
||||
is FloatType -> builtIns.floatType
|
||||
is ByteType -> builtIns.byteType
|
||||
is IntegerType -> builtIns.intType
|
||||
is BooleanType -> builtIns.booleanType
|
||||
is ShortType -> builtIns.shortType
|
||||
is ArrayType -> {
|
||||
when (val componentType = type.componentType()) {
|
||||
is VoidType -> builtIns.getArrayType(Variance.INVARIANT, builtIns.unitType)
|
||||
is LongType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.LONG)
|
||||
is DoubleType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.DOUBLE)
|
||||
is CharType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.CHAR)
|
||||
is FloatType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.FLOAT)
|
||||
is ByteType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.BYTE)
|
||||
is IntegerType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.INT)
|
||||
is BooleanType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.BOOLEAN)
|
||||
is ShortType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.SHORT)
|
||||
else -> builtIns.getArrayType(Variance.INVARIANT, convertReferenceType(componentType))
|
||||
}
|
||||
}
|
||||
is ReferenceType -> convertReferenceType(type)
|
||||
else -> builtIns.anyType
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertReferenceType(type: JdiType): KotlinType {
|
||||
require(type is ClassType || type is InterfaceType)
|
||||
|
||||
val asmType = AsmType.getType(type.signature())
|
||||
val project = codeFragment.project
|
||||
val classDescriptor = asmType.getClassDescriptor(GlobalSearchScope.allScope(project), mapBuiltIns = false)
|
||||
?: return moduleDescriptor.builtIns.nullableAnyType
|
||||
return classDescriptor.defaultType
|
||||
}
|
||||
}
|
||||
|
||||
private object DebugLabelModuleDescriptor
|
||||
: DeclarationDescriptorImpl(Annotations.EMPTY, Name.identifier("DebugLabelExtensions")),
|
||||
ModuleDescriptor
|
||||
{
|
||||
override val builtIns: KotlinBuiltIns
|
||||
get() = DefaultBuiltIns.Instance
|
||||
|
||||
override val stableName: Name?
|
||||
get() = name
|
||||
|
||||
override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor) = false
|
||||
|
||||
override fun getPackage(fqName: FqName): PackageViewDescriptor {
|
||||
return object : PackageViewDescriptor, DeclarationDescriptorImpl(Annotations.EMPTY, FqName.ROOT.shortNameOrSpecial()) {
|
||||
override fun getContainingDeclaration(): PackageViewDescriptor? = null
|
||||
|
||||
override val fqName: FqName
|
||||
get() = FqName.ROOT
|
||||
|
||||
override val memberScope: MemberScope
|
||||
get() = MemberScope.Empty
|
||||
|
||||
override val module: ModuleDescriptor
|
||||
get() = this@DebugLabelModuleDescriptor
|
||||
|
||||
override val fragments: List<PackageFragmentDescriptor>
|
||||
get() = emptyList()
|
||||
|
||||
override fun <R : Any?, D : Any?> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R {
|
||||
return visitor.visitPackageViewDescriptor(this, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val platform: TargetPlatform?
|
||||
get() = JvmPlatforms.unspecifiedJvmPlatform
|
||||
|
||||
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override val allDependencyModules: List<ModuleDescriptor>
|
||||
get() = emptyList()
|
||||
|
||||
override val expectedByModules: List<ModuleDescriptor>
|
||||
get() = emptyList()
|
||||
|
||||
override fun <T> getCapability(capability: ModuleDescriptor.Capability<T>): T? = null
|
||||
|
||||
override val isValid: Boolean
|
||||
get() = true
|
||||
|
||||
override fun assertValid() {}
|
||||
}
|
||||
|
||||
internal class DebugLabelPropertyDescriptor(
|
||||
containingDeclaration: DeclarationDescriptor,
|
||||
val labelName: String
|
||||
) : PropertyDescriptorImpl(
|
||||
containingDeclaration,
|
||||
null,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PUBLIC,
|
||||
/*isVar = */false,
|
||||
Name.identifier(labelName + "_DebugLabel"),
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
SourceElement.NO_SOURCE,
|
||||
/*lateInit = */false,
|
||||
/*isConst = */false,
|
||||
/*isExpect = */false,
|
||||
/*isActual = */false,
|
||||
/*isExternal = */false,
|
||||
/*isDelegated = */false
|
||||
)
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.kotlin.idea.debugger.evaluate.compilingEvaluator
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
|
||||
fun loadClassesSafely(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
return try {
|
||||
loadClasses(context, classes)
|
||||
} catch (e: EvaluateException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
LOG.debug("Failed to evaluate expression", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
if (classes.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ClassLoadingAdapter.loadClasses(context, classes)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.surroundWith;
|
||||
|
||||
import com.intellij.lang.surroundWith.Surrounder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.idea.core.surroundWith.KotlinExpressionSurroundDescriptorBase;
|
||||
import org.jetbrains.kotlin.idea.debugger.surroundWith.KotlinRuntimeTypeCastSurrounder;
|
||||
|
||||
public class KotlinDebuggerExpressionSurroundDescriptor extends KotlinExpressionSurroundDescriptorBase {
|
||||
|
||||
private static final Surrounder[] SURROUNDERS = {
|
||||
new KotlinRuntimeTypeCastSurrounder()
|
||||
};
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Surrounder[] getSurrounders() {
|
||||
return SURROUNDERS;
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.surroundWith
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightBundle
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerInvocationUtil
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.openapi.application.Result
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ScrollType
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.util.ProgressWindow
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinRuntimeTypeEvaluator
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.surroundWith.KotlinExpressionSurrounder
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||
|
||||
class KotlinRuntimeTypeCastSurrounder: KotlinExpressionSurrounder() {
|
||||
|
||||
override fun isApplicable(expression: KtExpression): Boolean {
|
||||
if (!super.isApplicable(expression)) return false
|
||||
|
||||
if (!expression.isPhysical) return false
|
||||
val file = expression.containingFile
|
||||
if (file !is KtCodeFragment) return false
|
||||
|
||||
val type = expression.analyze(BodyResolveMode.PARTIAL).getType(expression) ?: return false
|
||||
|
||||
return TypeUtils.canHaveSubtypes(KotlinTypeChecker.DEFAULT, type)
|
||||
}
|
||||
|
||||
override fun surroundExpression(project: Project, editor: Editor, expression: KtExpression): TextRange? {
|
||||
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val debuggerSession = debuggerContext.debuggerSession
|
||||
if (debuggerSession != null) {
|
||||
val progressWindow = ProgressWindow(true, expression.project)
|
||||
val worker = SurroundWithCastWorker(editor, expression, debuggerContext, progressWindow)
|
||||
progressWindow.title = DebuggerBundle.message("title.evaluating")
|
||||
debuggerContext.debugProcess?.managerThread?.startProgress(worker, progressWindow)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getTemplateDescription(): String {
|
||||
return KotlinBundle.message("surround.with.runtime.type.cast.template")
|
||||
}
|
||||
|
||||
private inner class SurroundWithCastWorker(
|
||||
private val myEditor: Editor,
|
||||
expression: KtExpression,
|
||||
context: DebuggerContextImpl,
|
||||
indicator: ProgressIndicator
|
||||
): KotlinRuntimeTypeEvaluator(myEditor, expression, context, indicator) {
|
||||
|
||||
override fun typeCalculationFinished(type: KotlinType?) {
|
||||
if (type == null) return
|
||||
|
||||
hold()
|
||||
|
||||
val project = myEditor.project
|
||||
DebuggerInvocationUtil.invokeLater(project, Runnable {
|
||||
object : WriteCommandAction<Any>(project, CodeInsightBundle.message("command.name.surround.with.runtime.cast")) {
|
||||
override fun run(result: Result<Any>) {
|
||||
try {
|
||||
val factory = KtPsiFactory(myElement.project)
|
||||
|
||||
val fqName = DescriptorUtils.getFqName(type.constructor.declarationDescriptor!!)
|
||||
val parentCast = factory.createExpression("(expr as " + fqName.asString() + ")") as KtParenthesizedExpression
|
||||
val cast = parentCast.expression as KtBinaryExpressionWithTypeRHS
|
||||
cast.left.replace(myElement)
|
||||
val expr = myElement.replace(parentCast) as KtExpression
|
||||
|
||||
ShortenReferences.DEFAULT.process(expr)
|
||||
|
||||
val range = expr.textRange
|
||||
myEditor.selectionModel.setSelection(range.startOffset, range.endOffset)
|
||||
myEditor.caretModel.moveToOffset(range.endOffset)
|
||||
myEditor.scrollingModel.scrollToCaret(ScrollType.RELATIVE)
|
||||
}
|
||||
finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
}.execute()
|
||||
}, myProgressIndicator.modalityState)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.variables
|
||||
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Result
|
||||
import org.jetbrains.kotlin.idea.debugger.isSubtype
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import com.sun.jdi.Type as JdiType
|
||||
import kotlin.jvm.internal.Ref
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
class EvaluatorValueConverter(private val context: ExecutionContext) {
|
||||
private companion object {
|
||||
private val UNBOXING_METHOD_NAMES = mapOf(
|
||||
"java/lang/Boolean" to "booleanValue",
|
||||
"java/lang/Character" to "charValue",
|
||||
"java/lang/Byte" to "byteValue",
|
||||
"java/lang/Short" to "shortValue",
|
||||
"java/lang/Integer" to "intValue",
|
||||
"java/lang/Float" to "floatValue",
|
||||
"java/lang/Long" to "longValue",
|
||||
"java/lang/Double" to "doubleValue"
|
||||
)
|
||||
}
|
||||
|
||||
// Nearly accurate: doesn't do deep checks for Ref wrappers. Use `coerce()` for more precise check.
|
||||
fun typeMatches(requestedType: AsmType, actualTypeObj: JdiType?): Boolean {
|
||||
if (actualTypeObj == null) return true
|
||||
|
||||
// Main path
|
||||
if (requestedType.descriptor == "Ljava/lang/Object;" || actualTypeObj.isSubtype(requestedType)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val actualType = actualTypeObj.asmType()
|
||||
|
||||
fun isRefWrapper(wrapperType: AsmType, objType: AsmType): Boolean {
|
||||
return !objType.isPrimitiveType && wrapperType.className == Ref.ObjectRef::class.java.name
|
||||
}
|
||||
|
||||
if (isRefWrapper(actualType, requestedType) || isRefWrapper(requestedType, actualType)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val unwrappedActualType = unwrap(actualType)
|
||||
val unwrappedRequestedType = unwrap(requestedType)
|
||||
return unwrappedActualType == unwrappedRequestedType
|
||||
}
|
||||
|
||||
fun coerce(value: Value?, type: AsmType): Result? {
|
||||
val unrefResult = coerceRef(value, type) ?: return null
|
||||
return coerceBoxing(unrefResult.value, type)
|
||||
}
|
||||
|
||||
private fun coerceRef(value: Value?, type: AsmType): Result? {
|
||||
when {
|
||||
type.isRefType -> {
|
||||
if (value != null && value.asmType().isRefType) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
return Result(ref(value))
|
||||
}
|
||||
value != null && value.asmType().isRefType -> {
|
||||
if (type.isRefType) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
return Result(unref(value))
|
||||
}
|
||||
else -> return Result(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun coerceBoxing(value: Value?, type: AsmType): Result? {
|
||||
when {
|
||||
value == null -> return Result(value)
|
||||
type == AsmType.VOID_TYPE -> return Result(context.vm.mirrorOfVoid())
|
||||
type.isBoxedType -> {
|
||||
if (value.asmType().isBoxedType) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
if (value !is PrimitiveValue) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Result(box(value))
|
||||
}
|
||||
type.isPrimitiveType -> {
|
||||
if (value is PrimitiveValue) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
if (value !is ObjectReference || !value.asmType().isBoxedType) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Result(unbox(value))
|
||||
}
|
||||
value is PrimitiveValue -> {
|
||||
if (type.sort != AsmType.OBJECT) {
|
||||
return null
|
||||
}
|
||||
|
||||
val boxedValue = box(value)
|
||||
if (!typeMatches(type, boxedValue?.type())) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Result(boxedValue)
|
||||
}
|
||||
else -> return Result(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun box(value: Value?): Value? {
|
||||
if (value !is PrimitiveValue) {
|
||||
return value
|
||||
}
|
||||
|
||||
val unboxedType = value.asmType()
|
||||
val boxedType = box(unboxedType)
|
||||
|
||||
val boxedTypeClass = (context.findClass(boxedType) as ClassType?)
|
||||
?: error("Class $boxedType is not loaded")
|
||||
|
||||
val methodDesc = AsmType.getMethodDescriptor(boxedType, unboxedType)
|
||||
val valueOfMethod = boxedTypeClass.methodsByName("valueOf", methodDesc).first()
|
||||
|
||||
return context.invokeMethod(boxedTypeClass, valueOfMethod, listOf(value))
|
||||
}
|
||||
|
||||
private fun unbox(value: Value?): Value? {
|
||||
if (value !is ObjectReference) {
|
||||
return value
|
||||
}
|
||||
|
||||
val boxedTypeClass = value.referenceType() as? ClassType ?: return value
|
||||
val boxedType = boxedTypeClass.asmType().takeIf { it.isBoxedType } ?: return value
|
||||
val unboxedType = unbox(boxedType)
|
||||
|
||||
val unboxingMethodName = UNBOXING_METHOD_NAMES.getValue(boxedType.internalName)
|
||||
val methodDesc = AsmType.getMethodDescriptor(unboxedType)
|
||||
val valueMethod = boxedTypeClass.methodsByName(unboxingMethodName, methodDesc).first()
|
||||
return context.invokeMethod(value, valueMethod, emptyList())
|
||||
}
|
||||
|
||||
private fun ref(value: Value?): Value? {
|
||||
if (value is VoidValue) {
|
||||
return value
|
||||
}
|
||||
|
||||
fun wrapRef(value: Value?, refTypeClass: ClassType): Value? {
|
||||
val constructor = refTypeClass.methods().single { it.isConstructor }
|
||||
val ref = context.newInstance(refTypeClass, constructor, emptyList())
|
||||
context.keepReference(ref)
|
||||
|
||||
val elementField = refTypeClass.fieldByName("element") ?: error("'element' field not found")
|
||||
ref.setValue(elementField, value)
|
||||
return ref
|
||||
}
|
||||
|
||||
if (value is PrimitiveValue) {
|
||||
val primitiveType = value.asmType()
|
||||
val refType = PRIMITIVE_TO_REF.getValue(primitiveType)
|
||||
|
||||
val refTypeClass = (context.findClass(refType) as ClassType?)
|
||||
?: error("Class $refType is not loaded")
|
||||
|
||||
return wrapRef(value, refTypeClass)
|
||||
} else {
|
||||
val refType = AsmType.getType(Ref.ObjectRef::class.java)
|
||||
val refTypeClass = (context.findClass(refType) as ClassType?)
|
||||
?: error("Class $refType is not loaded")
|
||||
|
||||
return wrapRef(value, refTypeClass)
|
||||
}
|
||||
}
|
||||
|
||||
fun unref(value: Value?): Value? {
|
||||
if (value !is ObjectReference) {
|
||||
return value
|
||||
}
|
||||
|
||||
val type = value.type()
|
||||
if (type !is ClassType || !type.signature().startsWith("L" + AsmTypes.REF_TYPE_PREFIX)) {
|
||||
return value
|
||||
}
|
||||
|
||||
val field = type.fieldByName("element") ?: return value
|
||||
return value.getValue(field)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unbox(type: AsmType): AsmType {
|
||||
if (type.sort == AsmType.OBJECT) {
|
||||
return BOXED_TO_PRIMITIVE[type] ?: type
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
private fun box(type: AsmType): AsmType {
|
||||
if (type.isPrimitiveType) {
|
||||
return PRIMITIVE_TO_BOXED[type] ?: type
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
private fun unwrap(type: AsmType): AsmType {
|
||||
if (type.sort != AsmType.OBJECT) {
|
||||
return type
|
||||
}
|
||||
|
||||
return REF_TO_PRIMITIVE[type] ?: BOXED_TO_PRIMITIVE[type] ?: type
|
||||
}
|
||||
|
||||
private val AsmType.isPrimitiveType: Boolean
|
||||
get() = sort != AsmType.OBJECT && sort != AsmType.ARRAY
|
||||
|
||||
private val AsmType.isRefType: Boolean
|
||||
get() = sort == AsmType.OBJECT && this in REF_TYPES
|
||||
|
||||
private val AsmType.isBoxedType: Boolean
|
||||
get() = this in BOXED_TO_PRIMITIVE
|
||||
|
||||
private fun Value.asmType(): AsmType {
|
||||
return type().asmType()
|
||||
}
|
||||
|
||||
private fun JdiType.asmType(): AsmType {
|
||||
return AsmType.getType(signature())
|
||||
}
|
||||
|
||||
private val BOXED_TO_PRIMITIVE: Map<AsmType, AsmType> = JvmPrimitiveType.values()
|
||||
.map { Pair(AsmType.getObjectType(it.wrapperFqName.internalNameWithoutInnerClasses), AsmType.getType(it.desc)) }
|
||||
.toMap()
|
||||
|
||||
private val PRIMITIVE_TO_BOXED: Map<AsmType, AsmType> = BOXED_TO_PRIMITIVE.map { (k, v) -> Pair(v, k) }.toMap()
|
||||
|
||||
private val REF_TO_PRIMITIVE = mapOf(
|
||||
Ref.ByteRef::class.java.name to AsmType.BYTE_TYPE,
|
||||
Ref.ShortRef::class.java.name to AsmType.SHORT_TYPE,
|
||||
Ref.IntRef::class.java.name to AsmType.INT_TYPE,
|
||||
Ref.LongRef::class.java.name to AsmType.LONG_TYPE,
|
||||
Ref.FloatRef::class.java.name to AsmType.FLOAT_TYPE,
|
||||
Ref.DoubleRef::class.java.name to AsmType.DOUBLE_TYPE,
|
||||
Ref.CharRef::class.java.name to AsmType.CHAR_TYPE,
|
||||
Ref.BooleanRef::class.java.name to AsmType.BOOLEAN_TYPE
|
||||
).mapKeys { (k, _) -> AsmType.getObjectType(k.replace('.', '/')) }
|
||||
|
||||
private val PRIMITIVE_TO_REF: Map<AsmType, AsmType> = REF_TO_PRIMITIVE.map { (k, v) -> Pair(v, k) }.toMap()
|
||||
|
||||
private val REF_TYPES: Set<AsmType> = REF_TO_PRIMITIVE.keys + AsmType.getType(Ref.ObjectRef::class.java)
|
||||
+466
@@ -0,0 +1,466 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.variables
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.getCapturedFieldName
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.getLabeledThisName
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
||||
import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX
|
||||
import org.jetbrains.kotlin.idea.core.util.mergeAttachments
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import kotlin.coroutines.Continuation
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import com.sun.jdi.Type as JdiType
|
||||
|
||||
class VariableFinder(private val context: ExecutionContext) {
|
||||
private val frameProxy = context.frameProxy
|
||||
|
||||
companion object {
|
||||
private const val USE_UNSAFE_FALLBACK = true
|
||||
|
||||
fun variableNotFound(context: ExecutionContext, message: String): Exception {
|
||||
val frameProxy = context.frameProxy
|
||||
val location = frameProxy.safeLocation()
|
||||
val scope = context.debugProcess.searchScope
|
||||
|
||||
val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available"
|
||||
|
||||
val sourceName = location?.sourceName()
|
||||
val declaringTypeName = location?.declaringType()?.name()?.replace('.', '/')?.let { JvmClassName.byInternalName(it) }
|
||||
|
||||
val sourceFile = if (sourceName != null && declaringTypeName != null) {
|
||||
DebuggerUtils.findSourceFileForClassIncludeLibrarySources(context.project, scope, declaringTypeName, sourceName, location)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val sourceFileText = runReadAction { sourceFile?.text }
|
||||
|
||||
if (sourceName != null && sourceFileText != null) {
|
||||
val attachments = mergeAttachments(
|
||||
Attachment(sourceName, sourceFileText),
|
||||
Attachment("location.txt", locationText)
|
||||
)
|
||||
|
||||
LOG.error(message, attachments)
|
||||
}
|
||||
|
||||
return EvaluateExceptionUtil.createEvaluateException(message)
|
||||
}
|
||||
|
||||
private fun getCapturedVariableNameRegex(capturedName: String): Regex {
|
||||
val escapedName = Regex.escape(capturedName)
|
||||
val escapedSuffix = Regex.escape(INLINE_TRANSFORMATION_SUFFIX)
|
||||
return Regex("^$escapedName(?:$escapedSuffix)?$")
|
||||
}
|
||||
}
|
||||
|
||||
private val evaluatorValueConverter = EvaluatorValueConverter(context)
|
||||
|
||||
sealed class VariableKind(val asmType: AsmType) {
|
||||
abstract fun capturedNameMatches(name: String): Boolean
|
||||
|
||||
class Ordinary(val name: String, asmType: AsmType, val isDelegated: Boolean) : VariableKind(asmType) {
|
||||
private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name))
|
||||
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
|
||||
}
|
||||
|
||||
// TODO Support overloaded local functions
|
||||
class LocalFunction(val name: String, asmType: AsmType) : VariableKind(asmType) {
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
override fun capturedNameMatches(name: String) = name == "$" + name
|
||||
}
|
||||
|
||||
class UnlabeledThis(asmType: AsmType) : VariableKind(asmType) {
|
||||
override fun capturedNameMatches(name: String) =
|
||||
(name == AsmUtil.CAPTURED_RECEIVER_FIELD || name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD)))
|
||||
}
|
||||
|
||||
class OuterClassThis(asmType: AsmType) : VariableKind(asmType) {
|
||||
override fun capturedNameMatches(name: String) = false
|
||||
}
|
||||
|
||||
class FieldVar(val fieldName: String, asmType: AsmType) : VariableKind(asmType) {
|
||||
// Captured 'field' are not supported yet
|
||||
override fun capturedNameMatches(name: String) = false
|
||||
}
|
||||
|
||||
class ExtensionThis(val label: String, asmType: AsmType) : VariableKind(asmType) {
|
||||
val parameterName = getLabeledThisName(label, AsmUtil.LABELED_THIS_PARAMETER, AsmUtil.RECEIVER_PARAMETER_NAME)
|
||||
val fieldName = getLabeledThisName(label, getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD), AsmUtil.CAPTURED_RECEIVER_FIELD)
|
||||
|
||||
private val capturedNameRegex = getCapturedVariableNameRegex(fieldName)
|
||||
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
|
||||
}
|
||||
}
|
||||
|
||||
class Result(val value: Value?)
|
||||
|
||||
private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) {
|
||||
companion object {
|
||||
fun of(field: Field, owner: ObjectReference): NamedEntity {
|
||||
return NamedEntity(field.name(), field.safeType()) { owner.getValue(field) }
|
||||
}
|
||||
|
||||
fun of(variable: LocalVariableProxyImpl, frameProxy: StackFrameProxyImpl): NamedEntity {
|
||||
return NamedEntity(variable.name(), variable.safeType()) { frameProxy.getValue(variable) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun find(parameter: CodeFragmentParameter, asmType: AsmType): Result? {
|
||||
return when (parameter.kind) {
|
||||
Kind.ORDINARY -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType, isDelegated = false))
|
||||
Kind.DELEGATED -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType, isDelegated = true))
|
||||
Kind.FAKE_JAVA_OUTER_CLASS -> thisObject()?.let { Result(it) }
|
||||
Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
|
||||
Kind.LOCAL_FUNCTION -> findLocalFunction(VariableKind.LocalFunction(parameter.name, asmType))
|
||||
Kind.DISPATCH_RECEIVER -> findDispatchThis(VariableKind.OuterClassThis(asmType))
|
||||
Kind.COROUTINE_CONTEXT -> findCoroutineContext()
|
||||
Kind.FIELD_VAR -> findFieldVariable(VariableKind.FieldVar(parameter.name, asmType))
|
||||
Kind.DEBUG_LABEL -> findDebugLabel(parameter.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findOrdinary(kind: VariableKind.Ordinary): Result? {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
|
||||
// Local variables – direct search
|
||||
findLocalVariable(variables, kind, kind.name)?.let { return it }
|
||||
|
||||
// Recursive search in local receiver variables
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
|
||||
// Recursive search in captured this
|
||||
val containingThis = thisObject() ?: return null
|
||||
return findCapturedVariable(kind, containingThis)
|
||||
}
|
||||
|
||||
private fun findFieldVariable(kind: VariableKind.FieldVar): Result? {
|
||||
val thisObject = thisObject()
|
||||
if (thisObject != null) {
|
||||
val field = thisObject.referenceType().fieldByName(kind.fieldName) ?: return null
|
||||
return Result(thisObject.getValue(field))
|
||||
} else {
|
||||
val containingType = frameProxy.safeLocation()?.declaringType() ?: return null
|
||||
val field = containingType.fieldByName(kind.fieldName) ?: return null
|
||||
return Result(containingType.getValue(field))
|
||||
}
|
||||
}
|
||||
|
||||
private fun findLocalFunction(kind: VariableKind.LocalFunction): Result? {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
|
||||
// Local variables – direct search, new convention
|
||||
val newConventionName = AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX + kind.name
|
||||
findLocalVariable(variables, kind, newConventionName)?.let { return it }
|
||||
|
||||
// Local variables – direct search, old convention (before 1.3.30)
|
||||
findLocalVariable(variables, kind, kind.name + "$")?.let { return it }
|
||||
|
||||
// Recursive search in local receiver variables
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
|
||||
// Recursive search in captured this
|
||||
val containingThis = thisObject() ?: return null
|
||||
return findCapturedVariable(kind, containingThis)
|
||||
}
|
||||
|
||||
private fun findExtensionThis(kind: VariableKind.ExtensionThis): Result? {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
|
||||
// Local variables – direct search
|
||||
val namePredicate = fun(name: String) = name == kind.parameterName || name.startsWith(kind.parameterName + '$')
|
||||
findLocalVariable(variables, kind, namePredicate)?.let { return it }
|
||||
|
||||
// Recursive search in local receiver variables
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
|
||||
// Recursive search in captured this
|
||||
val containingThis = thisObject()
|
||||
if (containingThis != null) {
|
||||
findCapturedVariable(kind, containingThis)?.let { return it }
|
||||
}
|
||||
|
||||
@Suppress("ConstantConditionIf")
|
||||
if (USE_UNSAFE_FALLBACK) {
|
||||
// Find an unlabeled this with the compatible type
|
||||
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findDispatchThis(kind: VariableKind.OuterClassThis): Result? {
|
||||
val containingThis = thisObject()
|
||||
if (containingThis != null) {
|
||||
findCapturedVariable(kind, containingThis)?.let { return it }
|
||||
}
|
||||
|
||||
if (isInsideDefaultImpls()) {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
findLocalVariable(variables, kind, AsmUtil.THIS_IN_DEFAULT_IMPLS)?.let { return it }
|
||||
}
|
||||
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
val inlineDepth = getInlineDepth(variables)
|
||||
|
||||
if (inlineDepth > 0) {
|
||||
variables.namedEntitySequence()
|
||||
.filter { it.name.matches(INLINED_THIS_REGEX) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
@Suppress("ConstantConditionIf")
|
||||
if (USE_UNSAFE_FALLBACK) {
|
||||
// Find an unlabeled this with the compatible type
|
||||
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findDebugLabel(name: String): Result? {
|
||||
val markupMap = DebugLabelPropertyDescriptorProvider.getMarkupMap(context.debugProcess)
|
||||
|
||||
for ((value, markup) in markupMap) {
|
||||
if (markup.text == name) {
|
||||
return Result(value)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findUnlabeledThis(kind: VariableKind.UnlabeledThis): Result? {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
|
||||
// Recursive search in local receiver variables
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
|
||||
val containingThis = thisObject() ?: return null
|
||||
return findCapturedVariable(kind, containingThis)
|
||||
}
|
||||
|
||||
private fun findLocalVariable(variables: List<LocalVariableProxyImpl>, kind: VariableKind, name: String): Result? {
|
||||
return findLocalVariable(variables, kind) { it == name }
|
||||
}
|
||||
|
||||
private fun findLocalVariable(
|
||||
variables: List<LocalVariableProxyImpl>,
|
||||
kind: VariableKind,
|
||||
namePredicate: (String) -> Boolean
|
||||
): Result? {
|
||||
val inlineDepth = getInlineDepth(variables)
|
||||
|
||||
if (inlineDepth > 0) {
|
||||
val inlineAwareNamePredicate = fun(name: String): Boolean {
|
||||
var endIndex = name.length
|
||||
var depth = 0
|
||||
|
||||
val suffixLen = INLINE_FUN_VAR_SUFFIX.length
|
||||
while (endIndex >= suffixLen) {
|
||||
if (name.substring(endIndex - suffixLen, endIndex) != INLINE_FUN_VAR_SUFFIX) {
|
||||
break
|
||||
}
|
||||
|
||||
depth++
|
||||
endIndex -= suffixLen
|
||||
}
|
||||
|
||||
return namePredicate(name.take(endIndex))
|
||||
}
|
||||
|
||||
variables.namedEntitySequence()
|
||||
.filter { inlineAwareNamePredicate(it.name) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
variables.namedEntitySequence()
|
||||
.filter { namePredicate(it.name) && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isInsideDefaultImpls(): Boolean {
|
||||
val declaringType = frameProxy.safeLocation()?.declaringType() ?: return false
|
||||
return declaringType.name().endsWith(JvmAbi.DEFAULT_IMPLS_SUFFIX)
|
||||
}
|
||||
|
||||
private fun findCoroutineContext(): Result? {
|
||||
val method = frameProxy.safeLocation()?.safeMethod() ?: return null
|
||||
val result = findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method) ?: return null
|
||||
return Result(result)
|
||||
}
|
||||
|
||||
private fun findCoroutineContextForLambda(method: Method): ObjectReference? {
|
||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||
return null
|
||||
}
|
||||
|
||||
val thisObject = thisObject() ?: return null
|
||||
val thisType = thisObject.referenceType()
|
||||
|
||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return findCoroutineContextForContinuation(thisObject)
|
||||
}
|
||||
|
||||
private fun findCoroutineContextForMethod(method: Method): ObjectReference? {
|
||||
if (CONTINUATION_TYPE.descriptor + ")" !in method.signature()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||
return findCoroutineContextForContinuation(continuation)
|
||||
}
|
||||
|
||||
private fun findCoroutineContextForContinuation(continuation: ObjectReference): ObjectReference? {
|
||||
val continuationType = (continuation.referenceType() as? ClassType)
|
||||
?.allInterfaces()?.firstOrNull { it.name() == Continuation::class.java.name }
|
||||
?: return null
|
||||
|
||||
val getContextMethod = continuationType
|
||||
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
|
||||
?: return null
|
||||
|
||||
return context.invokeMethod(continuation, getContextMethod, emptyList()) as? ObjectReference
|
||||
}
|
||||
|
||||
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
|
||||
fun isReceiverOrPassedThis(name: String) =
|
||||
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
|| name == AsmUtil.RECEIVER_PARAMETER_NAME
|
||||
|| name == AsmUtil.THIS_IN_DEFAULT_IMPLS
|
||||
|| INLINED_THIS_REGEX.matches(name)
|
||||
|
||||
if (kind is VariableKind.ExtensionThis) {
|
||||
variables.namedEntitySequence()
|
||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
return variables.namedEntitySequence()
|
||||
.filter { isReceiverOrPassedThis(it.name) }
|
||||
.mapNotNull { findCapturedVariable(kind, it.value) }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
private fun findCapturedVariable(kind: VariableKind, parentFactory: () -> Value?): Result? {
|
||||
val parent = getUnwrapDelegate(kind, parentFactory)
|
||||
return findCapturedVariable(kind, parent)
|
||||
}
|
||||
|
||||
private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? {
|
||||
val acceptsParentValue = kind is VariableKind.UnlabeledThis || kind is VariableKind.OuterClassThis
|
||||
if (parent != null && acceptsParentValue && kind.typeMatches(parent.type())) {
|
||||
return Result(parent)
|
||||
}
|
||||
|
||||
val fields = (parent as? ObjectReference)?.referenceType()?.fields() ?: return null
|
||||
|
||||
if (kind !is VariableKind.OuterClassThis) {
|
||||
// Captured variables - direct search
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
|
||||
// Recursive search in captured receivers
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { isCapturedReceiverFieldName(it.name) }
|
||||
.mapNotNull { findCapturedVariable(kind, it.value) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
// Recursive search in outer and captured this
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { it.name == AsmUtil.THIS_IN_DEFAULT_IMPLS || it.name == AsmUtil.CAPTURED_THIS_FIELD }
|
||||
.mapNotNull { findCapturedVariable(kind, it.value) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getUnwrapDelegate(kind: VariableKind, valueFactory: () -> Value?): Value? {
|
||||
val rawValue = valueFactory()
|
||||
if (kind !is VariableKind.Ordinary || !kind.isDelegated) {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
val delegateValue = rawValue as? ObjectReference ?: return rawValue
|
||||
val getValueMethod = delegateValue.referenceType()
|
||||
.methodsByName("getValue", "()Ljava/lang/Object;").firstOrNull()
|
||||
?: return rawValue
|
||||
|
||||
return context.invokeMethod(delegateValue, getValueMethod, emptyList())
|
||||
}
|
||||
|
||||
private fun isCapturedReceiverFieldName(name: String): Boolean {
|
||||
return name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD))
|
||||
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
|
||||
}
|
||||
|
||||
private fun VariableKind.typeMatches(actualType: JdiType?): Boolean {
|
||||
if (this is VariableKind.Ordinary && isDelegated) {
|
||||
// We can't figure out the actual type of the value yet.
|
||||
// No worries: it will be checked again (and more carefully) in `unwrapAndCheck()`.
|
||||
return true
|
||||
}
|
||||
return evaluatorValueConverter.typeMatches(asmType, actualType)
|
||||
}
|
||||
|
||||
private fun NamedEntity.unwrapAndCheck(kind: VariableKind): Result? {
|
||||
return evaluatorValueConverter.coerce(getUnwrapDelegate(kind, value), kind.asmType)
|
||||
}
|
||||
|
||||
private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> {
|
||||
return asSequence().map { NamedEntity.of(it, owner) }
|
||||
}
|
||||
|
||||
private fun List<LocalVariableProxyImpl>.namedEntitySequence(): Sequence<NamedEntity> {
|
||||
return asSequence().map { NamedEntity.of(it, frameProxy) }
|
||||
}
|
||||
|
||||
private fun thisObject(): ObjectReference? {
|
||||
val thisObjectFromEvaluation = context.evaluationContext.computeThisObject() as? ObjectReference
|
||||
if (thisObjectFromEvaluation != null) {
|
||||
return thisObjectFromEvaluation
|
||||
}
|
||||
|
||||
return frameProxy.thisObject()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile(project(":compiler:backend"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(files("${System.getProperty("java.home")}/../lib/tools.jar"))
|
||||
|
||||
compileOnly(intellijPluginDep("stream-debugger"))
|
||||
|
||||
testCompile(project(":kotlin-test:kotlin-test-junit"))
|
||||
testCompile(commonDep("junit:junit"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { none() }
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.sequence.lib.collections
|
||||
|
||||
import com.intellij.debugger.streams.lib.IntermediateOperation
|
||||
import com.intellij.debugger.streams.lib.TerminalOperation
|
||||
import com.intellij.debugger.streams.lib.impl.LibrarySupportBase
|
||||
import com.intellij.debugger.streams.resolve.FilterResolver
|
||||
import com.intellij.debugger.streams.resolve.ValuesOrderResolver
|
||||
import com.intellij.debugger.streams.trace.CallTraceInterpreter
|
||||
import com.intellij.debugger.streams.trace.IntermediateCallHandler
|
||||
import com.intellij.debugger.streams.trace.TerminatorCallHandler
|
||||
import com.intellij.debugger.streams.trace.dsl.Dsl
|
||||
import com.intellij.debugger.streams.wrapper.IntermediateStreamCall
|
||||
import com.intellij.debugger.streams.wrapper.TerminatorStreamCall
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.collections.BothSemanticHandlerWrapper
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.collections.BothSemanticsHandler
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.collections.FilterCallHandler
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.interpret.FilterTraceInterpreter
|
||||
|
||||
class KotlinCollectionLibrarySupport : LibrarySupportBase() {
|
||||
init {
|
||||
addOperation(FilterOperation("filter", FilterCallHandler(), true))
|
||||
addOperation(FilterOperation("filterNot", FilterCallHandler(), false))
|
||||
}
|
||||
|
||||
private fun addOperation(operation: CollectionOperation) {
|
||||
addIntermediateOperationsSupport(operation)
|
||||
addTerminationOperationsSupport(operation)
|
||||
}
|
||||
|
||||
private abstract class CollectionOperation(
|
||||
override val name: String,
|
||||
handler: BothSemanticsHandler
|
||||
) : IntermediateOperation, TerminalOperation {
|
||||
|
||||
private val wrapper = BothSemanticHandlerWrapper(handler)
|
||||
|
||||
override fun getTraceHandler(callOrder: Int, call: IntermediateStreamCall, dsl: Dsl): IntermediateCallHandler =
|
||||
wrapper.createIntermediateHandler(callOrder, call, dsl)
|
||||
|
||||
override fun getTraceHandler(call: TerminatorStreamCall, resultExpression: String, dsl: Dsl): TerminatorCallHandler =
|
||||
wrapper.createTerminatorHandler(call, resultExpression, dsl)
|
||||
}
|
||||
|
||||
private class FilterOperation(name: String, handler: BothSemanticsHandler, valueToAccept: Boolean) :
|
||||
CollectionOperation(name, handler) {
|
||||
override val traceInterpreter: CallTraceInterpreter = FilterTraceInterpreter(valueToAccept)
|
||||
override val valuesOrderResolver: ValuesOrderResolver = FilterResolver()
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.sequence.lib.collections
|
||||
|
||||
import com.intellij.debugger.streams.lib.LibrarySupport
|
||||
import com.intellij.debugger.streams.lib.LibrarySupportProvider
|
||||
import com.intellij.debugger.streams.trace.TraceExpressionBuilder
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.DslImpl
|
||||
import com.intellij.debugger.streams.wrapper.StreamChainBuilder
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.collections.KotlinCollectionChainBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinCollectionsPeekCallFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinStatementFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.KotlinTraceExpressionBuilder
|
||||
|
||||
class KotlinCollectionSupportProvider : LibrarySupportProvider {
|
||||
private companion object {
|
||||
val builder: StreamChainBuilder = KotlinCollectionChainBuilder()
|
||||
val support: LibrarySupport = KotlinCollectionLibrarySupport()
|
||||
val dsl = DslImpl(KotlinStatementFactory(KotlinCollectionsPeekCallFactory()))
|
||||
}
|
||||
|
||||
override fun getLanguageId(): String = KotlinLanguage.INSTANCE.id
|
||||
|
||||
override fun getChainBuilder(): StreamChainBuilder = builder
|
||||
|
||||
override fun getLibrarySupport(): LibrarySupport = support
|
||||
|
||||
override fun getExpressionBuilder(project: Project): TraceExpressionBuilder =
|
||||
KotlinTraceExpressionBuilder(dsl, support.createHandlerFactory(dsl))
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.sequence.lib.java
|
||||
|
||||
import com.intellij.debugger.streams.lib.LibrarySupport
|
||||
import com.intellij.debugger.streams.lib.LibrarySupportProvider
|
||||
import com.intellij.debugger.streams.lib.impl.StandardLibrarySupport
|
||||
import com.intellij.debugger.streams.trace.TraceExpressionBuilder
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.DslImpl
|
||||
import com.intellij.debugger.streams.wrapper.StreamChainBuilder
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.KotlinChainTransformerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.PackageBasedCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.TerminatedChainBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.java.JavaStreamChainTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.java.StandardLibraryCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.JavaPeekCallFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinStatementFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.KotlinTraceExpressionBuilder
|
||||
|
||||
class JavaStandardLibrarySupportProvider : LibrarySupportProvider {
|
||||
private companion object {
|
||||
val builder = TerminatedChainBuilder(
|
||||
KotlinChainTransformerImpl(JavaStreamChainTypeExtractor()),
|
||||
StandardLibraryCallChecker(PackageBasedCallChecker("java.util.stream"))
|
||||
)
|
||||
val support = StandardLibrarySupport()
|
||||
val dsl = DslImpl(KotlinStatementFactory(JavaPeekCallFactory()))
|
||||
}
|
||||
|
||||
override fun getLanguageId(): String = KotlinLanguage.INSTANCE.id
|
||||
|
||||
override fun getChainBuilder(): StreamChainBuilder = builder
|
||||
|
||||
override fun getLibrarySupport(): LibrarySupport = support
|
||||
|
||||
override fun getExpressionBuilder(project: Project): TraceExpressionBuilder =
|
||||
KotlinTraceExpressionBuilder(dsl, support.createHandlerFactory(dsl))
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.sequence.lib.java
|
||||
|
||||
import com.intellij.debugger.streams.lib.LibrarySupport
|
||||
import com.intellij.debugger.streams.lib.LibrarySupportProvider
|
||||
import com.intellij.debugger.streams.lib.impl.StreamExLibrarySupport
|
||||
import com.intellij.debugger.streams.trace.TraceExpressionBuilder
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.DslImpl
|
||||
import com.intellij.debugger.streams.wrapper.StreamChainBuilder
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.KotlinChainTransformerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.PackageBasedCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.TerminatedChainBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.java.JavaStreamChainTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.java.StreamExCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.JavaPeekCallFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinStatementFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.KotlinTraceExpressionBuilder
|
||||
|
||||
class StreamExLibrarySupportProvider : LibrarySupportProvider {
|
||||
private companion object {
|
||||
val streamChainBuilder = TerminatedChainBuilder(
|
||||
KotlinChainTransformerImpl(JavaStreamChainTypeExtractor()),
|
||||
StreamExCallChecker(PackageBasedCallChecker("one.util.streamex"))
|
||||
)
|
||||
val support = StreamExLibrarySupport()
|
||||
val dsl = DslImpl(KotlinStatementFactory(JavaPeekCallFactory()))
|
||||
val expressionBuilder = KotlinTraceExpressionBuilder(dsl, support.createHandlerFactory(dsl))
|
||||
}
|
||||
|
||||
override fun getLanguageId(): String = KotlinLanguage.INSTANCE.id
|
||||
|
||||
override fun getChainBuilder(): StreamChainBuilder = streamChainBuilder
|
||||
|
||||
override fun getLibrarySupport(): LibrarySupport = support
|
||||
|
||||
override fun getExpressionBuilder(project: Project): TraceExpressionBuilder = expressionBuilder
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user