From dd4a8d01c9cbe6fff9b147f8d11f4715a0afdf93 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 21 Nov 2019 03:13:17 +0900 Subject: [PATCH] Debugger: Remove obsolete hacks needed for old Android dex --- .../idea/debugger/KotlinPositionManager.kt | 5 - .../stepping/DebuggerSteppingHelper.java | 11 +- .../stepping/KotlinSteppingCommandProvider.kt | 123 ++---------------- .../filter/KotlinStepOverInlineFilter.kt | 13 +- .../debugger/NoStrataPositionManagerHelper.kt | 74 ----------- 5 files changed, 18 insertions(+), 208 deletions(-) diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index e023efe3c5a..72d83eff98e 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -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 } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java index d9bd43f36e3..28fee4e4ffa 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java @@ -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(), diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt index 2c19901a197..cb5a65e1ab8 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -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, - 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 -} +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinStepOverInlineFilter.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinStepOverInlineFilter.kt index 436de199623..96f4fedb2fe 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinStepOverInlineFilter.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinStepOverInlineFilter.kt @@ -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, - val inlineRangeVariables: List, - val isDexDebug: Boolean, - val skipAfterCodeIndex: Long = -1 + val inlineRangeVariables: List ) 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 } diff --git a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt index d42bcc41bf8..08b634707bc 100644 --- a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt +++ b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt @@ -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 { - 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 { return ConcurrentFactoryMap.createWeakMap { key -> val bytes = readClassFileImpl(key.project, key.jvmName, key.file) ?: return@createWeakMap null @@ -245,34 +199,6 @@ private fun readLineNumberTableMapping(bytes: ByteArray): Map? { - 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 { val line = position.line val file = position.file