Debugger: Fix lvalue evaluation (KT-11663, KT-19980)

This commit is contained in:
Yan Zhulanow
2019-04-18 23:41:37 +03:00
parent bb0bc8a38a
commit 0a6a811c57
12 changed files with 174 additions and 51 deletions
@@ -36,6 +36,7 @@ class CodeFragmentCodegenInfo(
interface IParameter { interface IParameter {
val targetDescriptor: DeclarationDescriptor val targetDescriptor: DeclarationDescriptor
val targetType: KotlinType val targetType: KotlinType
val isLValue: Boolean
} }
} }
@@ -210,7 +211,7 @@ class CodeFragmentCodegen private constructor(
val asmType: Type val asmType: Type
val stackValue: StackValue val stackValue: StackValue
val sharedAsmType = getSharedTypeIfApplicable(parameter.targetDescriptor, typeMapper) val sharedAsmType = getSharedTypeIfApplicable(parameter, typeMapper)
if (sharedAsmType != null) { if (sharedAsmType != null) {
asmType = sharedAsmType asmType = sharedAsmType
val unwrappedType = typeMapper.mapType(parameter.targetType) val unwrappedType = typeMapper.mapType(parameter.targetType)
@@ -229,9 +230,15 @@ class CodeFragmentCodegen private constructor(
return CalculatedCodeFragmentCodegenInfo(parameters, methodSignature.returnType) return CalculatedCodeFragmentCodegenInfo(parameters, methodSignature.returnType)
} }
fun getSharedTypeIfApplicable(descriptor: DeclarationDescriptor, typeMapper: KotlinTypeMapper): Type? { fun getSharedTypeIfApplicable(parameter: IParameter, typeMapper: KotlinTypeMapper): Type? {
return when (descriptor) { return when (val descriptor = parameter.targetDescriptor) {
is LocalVariableDescriptor -> typeMapper.getSharedVarType(descriptor) is LocalVariableDescriptor -> {
var result = typeMapper.getSharedVarType(descriptor)
if (result == null && parameter.isLValue) {
result = StackValue.sharedTypeForType(typeMapper.mapType(descriptor.type))
}
result
}
else -> null else -> null
} }
} }
@@ -110,15 +110,21 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
return qualifiedExpression.getOperationSign() == KtTokens.DOT && return qualifiedExpression.getOperationSign() == KtTokens.DOT &&
qualifiedExpression.getReceiverExpression() == KtPsiUtil.deparenthesize(expression); qualifiedExpression.getReceiverExpression() == KtPsiUtil.deparenthesize(expression);
} }
if (parent instanceof KtBinaryExpression) {
KtBinaryExpression binaryExpression = (KtBinaryExpression) parent; return isLValue(expression, parent);
if (!OperatorConventions.BINARY_OPERATION_NAMES.containsKey(binaryExpression.getOperationToken()) && }
!KtTokens.ALL_ASSIGNMENTS.contains(binaryExpression.getOperationToken())) {
return false; public static boolean isLValue(@NotNull KtSimpleNameExpression expression, @Nullable PsiElement parent) {
} if (!(parent instanceof KtBinaryExpression)) {
return PsiTreeUtil.isAncestor(binaryExpression.getLeft(), expression, false); return false;
} }
return false;
KtBinaryExpression binaryExpression = (KtBinaryExpression) parent;
if (!OperatorConventions.BINARY_OPERATION_NAMES.containsKey(binaryExpression.getOperationToken()) &&
!KtTokens.ALL_ASSIGNMENTS.contains(binaryExpression.getOperationToken())) {
return false;
}
return PsiTreeUtil.isAncestor(binaryExpression.getLeft(), expression, false);
} }
private static boolean isDangerousWithNull(@NotNull KtSimpleNameExpression expression, @NotNull ExpressionTypingContext context) { private static boolean isDangerousWithNull(@NotNull KtSimpleNameExpression expression, @NotNull ExpressionTypingContext context) {
@@ -1463,7 +1469,9 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
@NotNull @NotNull
private KotlinTypeInfo assignmentIsNotAnExpressionError(KtBinaryExpression expression, ExpressionTypingContext context) { private KotlinTypeInfo assignmentIsNotAnExpressionError(KtBinaryExpression expression, ExpressionTypingContext context) {
facade.checkStatementType(expression, context); facade.checkStatementType(expression, context);
context.trace.report(ASSIGNMENT_IN_EXPRESSION_CONTEXT.on(expression)); if (!context.isDebuggerContext) {
context.trace.report(ASSIGNMENT_IN_EXPRESSION_CONTEXT.on(expression));
}
return TypeInfoFactoryKt.noTypeInfo(context); return TypeInfoFactoryKt.noTypeInfo(context);
} }
@@ -373,7 +373,9 @@ public class DataFlowAnalyzer {
public KotlinTypeInfo illegalStatementType(@NotNull KtExpression expression, @NotNull ExpressionTypingContext context, @NotNull ExpressionTypingInternals facade) { public KotlinTypeInfo illegalStatementType(@NotNull KtExpression expression, @NotNull ExpressionTypingContext context, @NotNull ExpressionTypingInternals facade) {
facade.checkStatementType( facade.checkStatementType(
expression, context.replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT)); expression, context.replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT));
context.trace.report(EXPRESSION_EXPECTED.on(expression, expression)); if (!context.isDebuggerContext) {
context.trace.report(EXPRESSION_EXPECTED.on(expression, expression));
}
return TypeInfoFactoryKt.noTypeInfo(context); return TypeInfoFactoryKt.noTypeInfo(context);
} }
@@ -55,6 +55,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConve
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
import org.jetbrains.kotlin.idea.debugger.safeLocation import org.jetbrains.kotlin.idea.debugger.safeLocation
import org.jetbrains.kotlin.idea.debugger.safeMethod import org.jetbrains.kotlin.idea.debugger.safeMethod
import org.jetbrains.kotlin.idea.debugger.safeVisibleVariableByName
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -287,18 +288,38 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
for (parameterType in compiledData.mainMethodSignature.parameterTypes) { for (parameterType in compiledData.mainMethodSignature.parameterTypes) {
context.findClass(parameterType, classLoader) context.findClass(parameterType, classLoader)
} }
val args = calculateMainMethodCallArguments(context, compiledData)
block(args) val variableFinder = VariableFinder(context)
val args = calculateMainMethodCallArguments(variableFinder, compiledData)
val result = block(args)
for (wrapper in variableFinder.refWrappers) {
updateLocalVariableValue(variableFinder.evaluatorValueConverter, wrapper)
}
return@executeWithBreakpointsDisabled result
} }
} }
private fun calculateMainMethodCallArguments(context: ExecutionContext, compiledData: CompiledDataDescriptor): List<Value?> { private fun updateLocalVariableValue(converter: EvaluatorValueConverter, ref: VariableFinder.RefWrapper) {
val frameProxy = converter.context.frameProxy
val variable = frameProxy.safeVisibleVariableByName(ref.localVariableName) ?: return
val newValue = converter.unref(ref.wrapper)
try {
frameProxy.setValue(variable, newValue)
} catch (e: InvalidTypeException) {
LOG.error("Cannot update local variable value: expected type ${variable.type}, actual type ${newValue?.type()}", e)
}
}
private fun calculateMainMethodCallArguments(variableFinder: VariableFinder, compiledData: CompiledDataDescriptor): List<Value?> {
val asmValueParameters = compiledData.mainMethodSignature.parameterTypes val asmValueParameters = compiledData.mainMethodSignature.parameterTypes
val valueParameters = compiledData.parameters val valueParameters = compiledData.parameters
require(asmValueParameters.size == valueParameters.size) require(asmValueParameters.size == valueParameters.size)
val args = valueParameters.zip(asmValueParameters) val args = valueParameters.zip(asmValueParameters)
val variableFinder = VariableFinder(context)
return args.map { (parameter, asmType) -> return args.map { (parameter, asmType) ->
val result = variableFinder.find(parameter, asmType) val result = variableFinder.find(parameter, asmType)
@@ -307,7 +328,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val name = parameter.debugString val name = parameter.debugString
fun isInsideDefaultInterfaceMethod(): Boolean { fun isInsideDefaultInterfaceMethod(): Boolean {
val method = context.frameProxy.safeLocation()?.safeMethod() ?: return false val method = variableFinder.context.frameProxy.safeLocation()?.safeMethod() ?: return false
val desc = method.signature() val desc = method.signature()
return method.name().endsWith("\$default") && DEFAULT_METHOD_MARKERS.any { desc.contains("I${it.descriptor})") } return method.name().endsWith("\$default") && DEFAULT_METHOD_MARKERS.any { desc.contains("I${it.descriptor})") }
} }
@@ -319,7 +340,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) { } else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) {
evaluationException("Parameter evaluation is not supported for '\$default' methods") evaluationException("Parameter evaluation is not supported for '\$default' methods")
} else { } else {
throw VariableFinder.variableNotFound(context, buildString { throw VariableFinder.variableNotFound(variableFinder.context, buildString {
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className) append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className)
}) })
} }
@@ -94,7 +94,7 @@ class CodeFragmentCompiler(private val executionContext: ExecutionContext) {
val classes = generationState.factory.asList().filterClassFiles() val classes = generationState.factory.asList().filterClassFiles()
.map { ClassToLoad(it.internalClassName, it.relativePath, it.asByteArray()) } .map { ClassToLoad(it.internalClassName, it.relativePath, it.asByteArray()) }
val methodSignature = getMethodSignature(methodDescriptor, parameterInfo.parameters, generationState) val methodSignature = getMethodSignature(methodDescriptor, parameterInfo, generationState)
val functionSuffixes = getLocalFunctionSuffixes(parameterInfo.parameters, generationState.typeMapper) val functionSuffixes = getLocalFunctionSuffixes(parameterInfo.parameters, generationState.typeMapper)
generationState.destroy() generationState.destroy()
@@ -123,13 +123,14 @@ class CodeFragmentCompiler(private val executionContext: ExecutionContext) {
private fun getMethodSignature( private fun getMethodSignature(
methodDescriptor: FunctionDescriptor, methodDescriptor: FunctionDescriptor,
parameters: List<CodeFragmentParameter.Smart>, parameterInfo: CodeFragmentParameterInfo,
state: GenerationState state: GenerationState
): MethodSignature { ): MethodSignature {
val typeMapper = state.typeMapper val typeMapper = state.typeMapper
val asmSignature = typeMapper.mapSignatureSkipGeneric(methodDescriptor) val asmSignature = typeMapper.mapSignatureSkipGeneric(methodDescriptor)
val asmParameters = parameters.zip(asmSignature.valueParameters).map { (param, sigParam) ->
getSharedTypeIfApplicable(param.targetDescriptor, typeMapper) ?: sigParam.asmType val asmParameters = parameterInfo.parameters.zip(asmSignature.valueParameters).map { (param, sigParam) ->
getSharedTypeIfApplicable(param, typeMapper) ?: sigParam.asmType
} }
return MethodSignature(asmParameters, asmSignature.returnType) return MethodSignature(asmParameters, asmSignature.returnType)
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument
@@ -34,6 +35,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.BasicExpressionTypingVisitor
import org.jetbrains.kotlin.types.expressions.createFunctionType import org.jetbrains.kotlin.types.expressions.createFunctionType
class CodeFragmentParameterInfo( class CodeFragmentParameterInfo(
@@ -140,7 +142,8 @@ class CodeFragmentParameterAnalyzer(
private fun processDescriptor(descriptor: DeclarationDescriptor, expression: KtSimpleNameExpression) { private fun processDescriptor(descriptor: DeclarationDescriptor, expression: KtSimpleNameExpression) {
val parameter = processDebugLabel(descriptor) val parameter = processDebugLabel(descriptor)
?: processCoroutineContextCall(descriptor) ?: processCoroutineContextCall(descriptor)
?: processSimpleNameExpression(descriptor) ?: processSimpleNameExpression(descriptor, expression)
checkBounds(descriptor, expression, parameter) checkBounds(descriptor, expression, parameter)
} }
@@ -255,7 +258,7 @@ class CodeFragmentParameterAnalyzer(
} }
} }
private fun processSimpleNameExpression(target: DeclarationDescriptor): Smart? { private fun processSimpleNameExpression(target: DeclarationDescriptor, expression: KtSimpleNameExpression): Smart? {
if (target is ValueParameterDescriptor && target.isCrossinline) { if (target is ValueParameterDescriptor && target.isCrossinline) {
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'crossinline' lambdas is not supported") throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'crossinline' lambdas is not supported")
} }
@@ -278,11 +281,14 @@ class CodeFragmentParameterAnalyzer(
} }
} }
is ValueDescriptor -> { is ValueDescriptor -> {
val parent = PsiTreeUtil.skipParentsOfType(expression, KtParenthesizedExpression::class.java)
val isLValue = BasicExpressionTypingVisitor.isLValue(expression, parent)
parameters.getOrPut(target) { parameters.getOrPut(target) {
val type = target.type val type = target.type
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
val kind = if (target is LocalVariableDescriptor && target.isDelegated) Kind.DELEGATED else Kind.ORDINARY val kind = if (target is LocalVariableDescriptor && target.isDelegated) Kind.DELEGATED else Kind.ORDINARY
Smart(Dumb(kind, target.name.asString()), type, target) Smart(Dumb(kind, target.name.asString()), type, target, isLValue)
} }
} }
else -> null else -> null
@@ -17,7 +17,7 @@ import com.sun.jdi.Type as JdiType
import kotlin.jvm.internal.Ref import kotlin.jvm.internal.Ref
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
class EvaluatorValueConverter(private val context: ExecutionContext) { class EvaluatorValueConverter(val context: ExecutionContext) {
private companion object { private companion object {
private val UNBOXING_METHOD_NAMES = mapOf( private val UNBOXING_METHOD_NAMES = mapOf(
"java/lang/Boolean" to "booleanValue", "java/lang/Boolean" to "booleanValue",
@@ -228,9 +228,12 @@ private fun unwrap(type: AsmType): AsmType {
private val AsmType.isPrimitiveType: Boolean private val AsmType.isPrimitiveType: Boolean
get() = sort != AsmType.OBJECT && sort != AsmType.ARRAY get() = sort != AsmType.OBJECT && sort != AsmType.ARRAY
private val AsmType.isRefType: Boolean internal val AsmType.isRefType: Boolean
get() = sort == AsmType.OBJECT && this in REF_TYPES get() = sort == AsmType.OBJECT && this in REF_TYPES
internal val Value?.isRefType: Boolean
get() = this is ObjectReference && AsmType.getType(this.referenceType().signature()).isRefType
private val AsmType.isBoxedType: Boolean private val AsmType.isBoxedType: Boolean
get() = this in BOXED_TO_PRIMITIVE get() = this in BOXED_TO_PRIMITIVE
@@ -30,11 +30,12 @@ import kotlin.coroutines.Continuation
import org.jetbrains.org.objectweb.asm.Type as AsmType import org.jetbrains.org.objectweb.asm.Type as AsmType
import com.sun.jdi.Type as JdiType import com.sun.jdi.Type as JdiType
class VariableFinder(private val context: ExecutionContext) { class VariableFinder(val context: ExecutionContext) {
private val frameProxy = context.frameProxy private val frameProxy = context.frameProxy
companion object { companion object {
private const val USE_UNSAFE_FALLBACK = true private val USE_UNSAFE_FALLBACK: Boolean
get() = true
fun variableNotFound(context: ExecutionContext, message: String): Exception { fun variableNotFound(context: ExecutionContext, message: String): Exception {
val frameProxy = context.frameProxy val frameProxy = context.frameProxy
@@ -73,7 +74,14 @@ class VariableFinder(private val context: ExecutionContext) {
} }
} }
private val evaluatorValueConverter = EvaluatorValueConverter(context) val evaluatorValueConverter = EvaluatorValueConverter(context)
val refWrappers: List<RefWrapper>
get() = mutableRefWrappers
private val mutableRefWrappers = mutableListOf<RefWrapper>()
class RefWrapper(val localVariableName: String, val wrapper: Value?)
sealed class VariableKind(val asmType: AsmType) { sealed class VariableKind(val asmType: AsmType) {
abstract fun capturedNameMatches(name: String): Boolean abstract fun capturedNameMatches(name: String): Boolean
@@ -273,8 +281,26 @@ class VariableFinder(private val context: ExecutionContext) {
): Result? { ): Result? {
val inlineDepth = getInlineDepth(variables) val inlineDepth = getInlineDepth(variables)
findLocalVariable(variables, kind, inlineDepth, namePredicate)?.let { return it }
// Try to find variables outside of inline functions as well
if (inlineDepth > 0 && USE_UNSAFE_FALLBACK) {
findLocalVariable(variables, kind, 0, namePredicate)?.let { return it }
}
return null
}
private fun findLocalVariable(
variables: List<LocalVariableProxyImpl>,
kind: VariableKind,
inlineDepth: Int,
namePredicate: (String) -> Boolean
): Result? {
val actualPredicate: (String) -> Boolean
if (inlineDepth > 0) { if (inlineDepth > 0) {
val inlineAwareNamePredicate = fun(name: String): Boolean { actualPredicate = fun(name: String): Boolean {
var endIndex = name.length var endIndex = name.length
var depth = 0 var depth = 0
@@ -288,21 +314,27 @@ class VariableFinder(private val context: ExecutionContext) {
endIndex -= suffixLen endIndex -= suffixLen
} }
return namePredicate(name.take(endIndex)) return namePredicate(name.take(endIndex)) && getInlineDepth(name) == inlineDepth
} }
} else {
variables.namedEntitySequence() actualPredicate = namePredicate
.filter { inlineAwareNamePredicate(it.name) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
} }
variables.namedEntitySequence() for (item in variables.namedEntitySequence()) {
.filter { namePredicate(it.name) && kind.typeMatches(it.type) } if (!actualPredicate(item.name) || !kind.typeMatches(item.type)) {
.mapNotNull { it.unwrapAndCheck(kind) } continue
.firstOrNull() }
?.let { return it }
val rawValue = item.value()
val result = evaluatorValueConverter.coerce(getUnwrapDelegate(kind, rawValue), kind.asmType) ?: continue
if (!rawValue.isRefType && result.value.isRefType) {
// Local variable was wrapped into a Ref instance
mutableRefWrappers += RefWrapper(item.name, result.value)
}
return result
}
return null return null
} }
@@ -377,7 +409,7 @@ class VariableFinder(private val context: ExecutionContext) {
} }
private fun findCapturedVariable(kind: VariableKind, parentFactory: () -> Value?): Result? { private fun findCapturedVariable(kind: VariableKind, parentFactory: () -> Value?): Result? {
val parent = getUnwrapDelegate(kind, parentFactory) val parent = getUnwrapDelegate(kind, parentFactory())
return findCapturedVariable(kind, parent) return findCapturedVariable(kind, parent)
} }
@@ -415,8 +447,7 @@ class VariableFinder(private val context: ExecutionContext) {
return null return null
} }
private fun getUnwrapDelegate(kind: VariableKind, valueFactory: () -> Value?): Value? { private fun getUnwrapDelegate(kind: VariableKind, rawValue: Value?): Value? {
val rawValue = valueFactory()
if (kind !is VariableKind.Ordinary || !kind.isDelegated) { if (kind !is VariableKind.Ordinary || !kind.isDelegated) {
return rawValue return rawValue
} }
@@ -443,8 +474,8 @@ class VariableFinder(private val context: ExecutionContext) {
return evaluatorValueConverter.typeMatches(asmType, actualType) return evaluatorValueConverter.typeMatches(asmType, actualType)
} }
private fun NamedEntity.unwrapAndCheck(kind: VariableKind): Result? { private fun NamedEntity.unwrapAndCheck(kind: VariableKind, value: () -> Value? = this.value): Result? {
return evaluatorValueConverter.coerce(getUnwrapDelegate(kind, value), kind.asmType) return evaluatorValueConverter.coerce(getUnwrapDelegate(kind, value()), kind.asmType)
} }
private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> { private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> {
@@ -22,7 +22,8 @@ interface CodeFragmentParameter {
class Smart( class Smart(
val dumb: Dumb, val dumb: Dumb,
override val targetType: KotlinType, override val targetType: KotlinType,
override val targetDescriptor: DeclarationDescriptor override val targetDescriptor: DeclarationDescriptor,
override val isLValue: Boolean = false
) : CodeFragmentParameter by dumb, CodeFragmentCodegenInfo.IParameter ) : CodeFragmentParameter by dumb, CodeFragmentCodegenInfo.IParameter
data class Dumb( data class Dumb(
@@ -0,0 +1,24 @@
package mutations
fun main() {
var a = 5
val b = 3
//Breakpoint!
val x = 0
//Breakpoint!
val y = 0
//Breakpoint!
val z = 0
}
// EXPRESSION: a = 100
// RESULT: VOID_VALUE
// EXPRESSION: b = 200
// RESULT: VOID_VALUE
// EXPRESSION: a + b
// RESULT: 300: I
@@ -0,0 +1,14 @@
LineBreakpoint created at mutations.kt:8
LineBreakpoint created at mutations.kt:11
LineBreakpoint created at mutations.kt:14
Run Java
Connected to the target VM
mutations.kt:8
Compile bytecode for a = 100
mutations.kt:11
Compile bytecode for b = 200
mutations.kt:14
Compile bytecode for a + b
Disconnected from the target VM
Process finished with exit code 0
@@ -1136,6 +1136,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt"); runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt");
} }
@TestMetadata("mutations.kt")
public void testMutations() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/mutations.kt");
}
@TestMetadata("nonCapturedVariables.kt") @TestMetadata("nonCapturedVariables.kt")
public void testNonCapturedVariables() throws Exception { public void testNonCapturedVariables() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/nonCapturedVariables.kt"); runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/nonCapturedVariables.kt");