Debugger: Remove obsolete hacks needed for old Android dex
This commit is contained in:
-5
@@ -138,11 +138,6 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
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
|
||||
}
|
||||
|
||||
+2
-9
@@ -37,7 +37,6 @@ import com.sun.jdi.request.StepRequest;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.debugger.NoStrataPositionManagerHelperKt;
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinSuspendCallStepOverFilter;
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral;
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction;
|
||||
@@ -56,17 +55,11 @@ public class DebuggerSteppingHelper {
|
||||
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
|
||||
);
|
||||
Action action = KotlinSteppingCommandProviderKt
|
||||
.getStepOverAction(frameProxy.location(), kotlinSourcePosition, frameProxy);
|
||||
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
|
||||
+14
-109
@@ -18,7 +18,6 @@ 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
|
||||
@@ -30,12 +29,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
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.debugger.stepping.filter.KotlinStepOverInlineFilter
|
||||
@@ -46,12 +39,10 @@ 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
|
||||
|
||||
@@ -281,19 +272,14 @@ interface KotlinMethodFilter : MethodFilter {
|
||||
fun locationMatches(context: SuspendContextImpl, location: Location): Boolean
|
||||
}
|
||||
|
||||
fun getStepOverAction(
|
||||
location: Location,
|
||||
kotlinSourcePosition: KotlinSourcePosition,
|
||||
frameProxy: StackFrameProxyImpl,
|
||||
isDexDebug: Boolean
|
||||
): Action {
|
||||
fun getStepOverAction(location: Location, kotlinSourcePosition: KotlinSourcePosition, frameProxy: StackFrameProxyImpl): Action {
|
||||
val inlineArgumentsToSkip = runReadAction {
|
||||
getInlineCallFunctionArgumentsIfAny(kotlinSourcePosition.sourcePosition)
|
||||
}
|
||||
|
||||
return getStepOverAction(
|
||||
location, kotlinSourcePosition.file, kotlinSourcePosition.linesRange,
|
||||
inlineArgumentsToSkip, frameProxy, isDexDebug
|
||||
inlineArgumentsToSkip, frameProxy
|
||||
)
|
||||
}
|
||||
|
||||
@@ -302,39 +288,27 @@ fun getStepOverAction(
|
||||
sourceFile: KtFile,
|
||||
range: IntRange,
|
||||
inlineFunctionArguments: List<KtElement>,
|
||||
frameProxy: StackFrameProxyImpl,
|
||||
isDexDebug: Boolean
|
||||
frameProxy: StackFrameProxyImpl
|
||||
): Action {
|
||||
location.declaringType() ?: return Action.StepOver()
|
||||
|
||||
val project = sourceFile.project
|
||||
|
||||
val methodLocations = location.method().safeAllLineLocations()
|
||||
if (methodLocations.isEmpty()) {
|
||||
return Action.StepOver()
|
||||
}
|
||||
|
||||
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()
|
||||
val ktLineNumber = nextLocation.lineNumber()
|
||||
if (ktLineNumber !in range) {
|
||||
return false
|
||||
}
|
||||
|
||||
return try {
|
||||
nextLocation.ktFileName() == sourceFile.name
|
||||
nextLocation.sourceName(KOTLIN_STRATA_NAME) == sourceFile.name
|
||||
} catch (e: AbsentInformationException) {
|
||||
true
|
||||
}
|
||||
@@ -346,22 +320,22 @@ fun getStepOverAction(
|
||||
.dropWhile { it != location }
|
||||
.drop(1)
|
||||
.filter(::isThisMethodLocation)
|
||||
.dropWhile { it.ktLineNumber() == location.ktLineNumber() }
|
||||
.dropWhile { it.lineNumber() == location.lineNumber() }
|
||||
.firstOrNull()
|
||||
|
||||
return previousSuitableLocation != null && previousSuitableLocation.ktLineNumber() > location.ktLineNumber()
|
||||
return previousSuitableLocation != null && previousSuitableLocation.lineNumber() > location.lineNumber()
|
||||
}
|
||||
|
||||
val patchedLocation = if (isBackEdgeLocation()) {
|
||||
// Pretend we had already done a backing step
|
||||
methodLocations
|
||||
.filter(::isThisMethodLocation)
|
||||
.firstOrNull { it.ktLineNumber() == location.ktLineNumber() } ?: location
|
||||
.firstOrNull { it.lineNumber() == location.lineNumber() } ?: location
|
||||
} else {
|
||||
location
|
||||
}
|
||||
|
||||
val patchedLineNumber = patchedLocation.ktLineNumber()
|
||||
val patchedLineNumber = patchedLocation.lineNumber()
|
||||
|
||||
val lambdaArgumentRanges = runReadAction {
|
||||
inlineFunctionArguments.map {
|
||||
@@ -384,34 +358,17 @@ fun getStepOverAction(
|
||||
val stepOverLocations = methodLocations
|
||||
.dropWhile { it != patchedLocation }
|
||||
.drop(1)
|
||||
.dropWhile { it.ktLineNumber() == patchedLineNumber }
|
||||
.dropWhile { it.lineNumber() == patchedLineNumber }
|
||||
.takeWhile { loc ->
|
||||
!isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it } || loc.ktLineNumber() == patchedLineNumber
|
||||
!isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.lineNumber() in it } || loc.lineNumber() == patchedLineNumber
|
||||
}
|
||||
|
||||
if (stepOverLocations.isNotEmpty()) {
|
||||
// Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that, being dex-processed, 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.StepOverInlined(
|
||||
StepOverFilterData(
|
||||
patchedLineNumber,
|
||||
stepOverLocations.map { it.ktLineNumber() }.toSet(),
|
||||
inlineRangeVariables,
|
||||
isDexDebug,
|
||||
returnCodeIndex
|
||||
stepOverLocations.map { it.lineNumber() }.toSet(),
|
||||
inlineRangeVariables
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -513,56 +470,4 @@ private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? {
|
||||
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 {
|
||||
const val RETURN_VOID = 0x0e
|
||||
const val RETURN = 0x0f
|
||||
const val RETURN_WIDE = 0x10
|
||||
const val RETURN_OBJECT = 0x11
|
||||
|
||||
const val GOTO = 0x28
|
||||
const val MOVE = 0x01
|
||||
}
|
||||
}
|
||||
+2
-11
@@ -22,29 +22,20 @@ 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
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinMethodFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.getInlineRangeLocalVariables
|
||||
|
||||
class StepOverFilterData(
|
||||
val lineNumber: Int,
|
||||
val stepOverLines: Set<Int>,
|
||||
val inlineRangeVariables: List<LocalVariable>,
|
||||
val isDexDebug: Boolean,
|
||||
val skipAfterCodeIndex: Long = -1
|
||||
val inlineRangeVariables: List<LocalVariable>
|
||||
)
|
||||
|
||||
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()
|
||||
val currentLine = location.lineNumber()
|
||||
if (!(data.stepOverLines.contains(currentLine))) {
|
||||
return currentLine != data.lineNumber
|
||||
}
|
||||
|
||||
-74
@@ -63,52 +63,6 @@ fun readBytecodeInfo(
|
||||
return KotlinDebuggerCaches.getOrReadDebugInfoFromBytecode(project, jvmName, file)
|
||||
}
|
||||
|
||||
fun ktLocationInfo(
|
||||
location: Location, isDexDebug: Boolean, project: Project,
|
||||
preferInlined: Boolean = false, locationFile: KtFile? = null
|
||||
): Pair<Int, KtFile?> {
|
||||
if (isDexDebug && (locationFile == null || location.lineNumber() > locationFile.getLineCount())) {
|
||||
if (!preferInlined) {
|
||||
val thisFunLine = runReadAction { getLastLineNumberForLocation(location, project) }
|
||||
if (thisFunLine != null && thisFunLine != location.lineNumber()) {
|
||||
return thisFunLine to locationFile
|
||||
}
|
||||
}
|
||||
|
||||
val inlinePosition = runReadAction { getOriginalPositionOfInlinedLine(location, project) }
|
||||
if (inlinePosition != null) {
|
||||
val (file, line) = inlinePosition
|
||||
return line + 1 to file
|
||||
}
|
||||
}
|
||||
|
||||
return location.lineNumber() to locationFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Only the first line number is stored for instruction in dex. It can be obtained through location.lineNumber().
|
||||
* This method allows to get last stored linenumber for instruction.
|
||||
*/
|
||||
fun getLastLineNumberForLocation(
|
||||
location: Location,
|
||||
project: Project,
|
||||
searchScope: GlobalSearchScope = GlobalSearchScope.allScope(project)
|
||||
): Int? {
|
||||
val lineNumber = location.lineNumber()
|
||||
val fqName = FqName(location.declaringType().name())
|
||||
val fileName = location.sourceName()
|
||||
|
||||
val method = location.method() ?: return null
|
||||
val name = method.name() ?: return null
|
||||
val signature = method.signature() ?: return null
|
||||
|
||||
val debugInfo =
|
||||
findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null
|
||||
|
||||
val lineMapping = debugInfo.lineTableMapping[BytecodeMethodKey(name, signature)] ?: return null
|
||||
return lineMapping.values.firstOrNull { it.contains(lineNumber) }?.last()
|
||||
}
|
||||
|
||||
fun createWeakBytecodeDebugInfoStorage(): ConcurrentMap<BinaryCacheKey, BytecodeDebugInfo?> {
|
||||
return ConcurrentFactoryMap.createWeakMap<BinaryCacheKey, BytecodeDebugInfo?> { key ->
|
||||
val bytes = readClassFileImpl(key.project, key.jvmName, key.file) ?: return@createWeakMap null
|
||||
@@ -245,34 +199,6 @@ private fun readLineNumberTableMapping(bytes: ByteArray): Map<BytecodeMethodKey,
|
||||
return lineNumberMapping
|
||||
}
|
||||
|
||||
internal fun getOriginalPositionOfInlinedLine(location: Location, project: Project): Pair<KtFile, Int>? {
|
||||
val lineNumber = location.lineNumber()
|
||||
val fqName = FqName(location.declaringType().name())
|
||||
val fileName = location.sourceName()
|
||||
val searchScope = GlobalSearchScope.allScope(project)
|
||||
|
||||
val debugInfo =
|
||||
findAndReadClassFile(fqName, fileName, project, searchScope) { isInlineFunctionLineNumber(it, lineNumber, project) } ?: return null
|
||||
val smapData = debugInfo.smapData ?: return null
|
||||
|
||||
return mapStacktraceLineToSource(smapData, lineNumber, project, SourceLineKind.EXECUTED_LINE, searchScope)
|
||||
}
|
||||
|
||||
private fun findAndReadClassFile(
|
||||
fqName: FqName, fileName: String, project: Project, searchScope: GlobalSearchScope,
|
||||
fileFilter: (VirtualFile) -> Boolean
|
||||
): BytecodeDebugInfo? {
|
||||
val internalName = fqName.asString().replace('.', '/')
|
||||
val jvmClassName = JvmClassName.byInternalName(internalName)
|
||||
|
||||
val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName) ?: return null
|
||||
|
||||
val virtualFile = file.virtualFile ?: return null
|
||||
if (!fileFilter(virtualFile)) return null
|
||||
|
||||
return readBytecodeInfo(project, jvmClassName, virtualFile)
|
||||
}
|
||||
|
||||
fun getLocationsOfInlinedLine(type: ReferenceType, position: SourcePosition, sourceSearchScope: GlobalSearchScope): List<Location> {
|
||||
val line = position.line
|
||||
val file = position.file
|
||||
|
||||
Reference in New Issue
Block a user