Debugger: Fix lvalue evaluation (KT-11663, KT-19980)
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-9
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-6
@@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -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)
|
||||||
|
|||||||
+9
-3
@@ -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
|
||||||
|
|||||||
+5
-2
@@ -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
|
||||||
|
|
||||||
|
|||||||
+52
-21
@@ -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> {
|
||||||
|
|||||||
+2
-1
@@ -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
|
||||||
+14
@@ -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
|
||||||
Generated
+5
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user