From fcf0b7a66c69e3c8c33ed0929c7f4e0c4afde8ae Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Fri, 15 Jul 2016 18:53:40 +0300 Subject: [PATCH] Refactoring: extract functions working with smap to separate file --- .../idea/filters/InlineSmapPositionUtil.kt | 129 ++++++++++++++++++ .../idea/filters/KotlinExceptionFilter.kt | 92 +------------ 2 files changed, 133 insertions(+), 88 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/filters/InlineSmapPositionUtil.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/filters/InlineSmapPositionUtil.kt b/idea/src/org/jetbrains/kotlin/idea/filters/InlineSmapPositionUtil.kt new file mode 100644 index 00000000000..50db343919b --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/filters/InlineSmapPositionUtil.kt @@ -0,0 +1,129 @@ +/* + * 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.filters + +import com.intellij.openapi.compiler.CompilerPaths +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.codegen.inline.FileMapping +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil +import org.jetbrains.kotlin.codegen.inline.SMAP +import org.jetbrains.kotlin.codegen.inline.SMAPParser +import org.jetbrains.kotlin.idea.refactoring.getLineCount +import org.jetbrains.kotlin.idea.refactoring.toPsiFile +import org.jetbrains.kotlin.idea.util.DebuggerUtils +import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.tail +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.utils.addToStdlib.check +import org.jetbrains.org.objectweb.asm.ClassReader +import org.jetbrains.org.objectweb.asm.ClassVisitor +import java.io.File + +fun readClassFile(jvmName: JvmClassName, file: VirtualFile, lineNumber: Int, project: Project): ByteArray? { + val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName) + + if (ProjectRootsUtil.isLibrarySourceFile(project, file)) { + val classId = ClassId(jvmName.packageFqName, Name.identifier(fqNameWithInners.asString())) + + val fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project) + val classFile = fileFinder.findVirtualFileWithHeader(classId) ?: return null + return classFile.contentsToByteArray() + } + + if (!ProjectRootsUtil.isProjectSourceFile(project, file)) return null + + val linesInFile = file.toPsiFile(project)?.getLineCount() ?: return null + if (lineNumber <= linesInFile) return null + + val module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(file) + val outputDir = CompilerPaths.getModuleOutputDirectory(module, /*forTests = */ false) ?: return null + + val className = fqNameWithInners.asString().replace('.', '$') + val classByByDirectory = findClassFileByPath(jvmName.packageFqName.asString(), className, outputDir) ?: return null + + return classByByDirectory.readBytes() +} + +private fun findClassFileByPath(packageName: String, className: String, outputDir: VirtualFile): File? { + val outDirFile = File(outputDir.path).check { it.exists() } ?: return null + + val parentDirectory = File(outDirFile, packageName.replace(".", File.separator)) + if (!parentDirectory.exists()) return null + + val classFile = File(parentDirectory, className + ".class") + if (classFile.exists()) { + return classFile + } + + return null +} + +fun parseStrata(strata: String?, line: Int, project: Project, isKotlin2: Boolean, searchScope: GlobalSearchScope): Pair? { + if (strata == null) return null + + val mappings = SMAPParser.parse(strata) + + val mappingInfo = mappings.fileMappings.firstOrNull { + it.getIntervalIfContains(line) != null + } ?: return null + + val newJvmName = JvmClassName.byInternalName(mappingInfo.path) + val newSourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, newJvmName, mappingInfo.name) ?: return null + + val interval = mappingInfo.getIntervalIfContains(line)!! + return newSourceFile to (if (isKotlin2) interval.source else interval.mapDestToSource(line)) - 1 +} + +fun readDebugInfo(bytes: ByteArray): SmapData? { + val cr = ClassReader(bytes) + var debugInfo: String? = null + cr.accept(object : ClassVisitor(InlineCodegenUtil.API) { + override fun visitSource(source: String?, debug: String?) { + debugInfo = debug + } + }, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE) + return debugInfo?.let { SmapData(it) } +} + +class SmapData(debugInfo: String) { + var kotlin1: String? = null + private set + var kotlin2: String? = null + private set + + init { + val intervals = debugInfo.split(SMAP.END).filter { it.isNotBlank() } + when(intervals.count()) { + 1 -> { + kotlin1 = intervals[0] + SMAP.END + } + else -> { + kotlin1 = intervals[0] + SMAP.END + kotlin2 = intervals[1] + SMAP.END + } + } + } +} + +private fun FileMapping.getIntervalIfContains(destLine: Int) = lineMappings.firstOrNull { it.contains(destLine) } diff --git a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt index 4620dd4459a..43b0e573d4f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt @@ -78,43 +78,8 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter } private fun getHyperlinkInfoIfInline(jvmName: JvmClassName, file: VirtualFile, lineNumber: Int, project: Project): InlineFunctionHyperLinkInfo? { - val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName) - - if (ProjectRootsUtil.isLibrarySourceFile(project, file)) { - val classId = ClassId(jvmName.packageFqName, Name.identifier(fqNameWithInners.asString())) - - val fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project) - val classFile = fileFinder.findVirtualFileWithHeader(classId) ?: return null - return readDebugInfoForInlineFun(classFile.contentsToByteArray(), lineNumber, project) - } - - if (!ProjectRootsUtil.isProjectSourceFile(project, file)) return null - - val linesInFile = file.toPsiFile(project)?.getLineCount() ?: return null - if (lineNumber <= linesInFile) return null - - - val module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(file) - val outputDir = CompilerPaths.getModuleOutputDirectory(module, /*forTests = */ false) ?: return null - - val className = fqNameWithInners.asString().replace('.', '$') - val classByByDirectory = findClassFileByPath(jvmName.packageFqName.asString(), className, outputDir) ?: return null - - return readDebugInfoForInlineFun(classByByDirectory.readBytes(), lineNumber, project) - } - - private fun findClassFileByPath(packageName: String, className: String, outputDir: VirtualFile): File? { - val outDirFile = File(outputDir.path).check { it.exists() } ?: return null - - val parentDirectory = File(outDirFile, packageName.replace(".", File.separator)) - if (!parentDirectory.exists()) return null - - val classFile = File(parentDirectory, className + ".class") - if (classFile.exists()) { - return classFile - } - - return null + val bytes = readClassFile(jvmName, file, lineNumber, project) ?: return null + return readDebugInfoForInlineFun(bytes, lineNumber, project) } private fun readDebugInfoForInlineFun(bytes: ByteArray, line: Int, project: Project): InlineFunctionHyperLinkInfo? { @@ -122,12 +87,12 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter val inlineInfo = arrayListOf() - val (inlineFunctionBodyFile, inlineFunctionBodyLine) = parseStrata(smapData.kotlin1, line, project, false) ?: return null + val (inlineFunctionBodyFile, inlineFunctionBodyLine) = parseStrata(smapData.kotlin1, line, project, false, searchScope) ?: return null inlineInfo.add(InlineFunctionHyperLinkInfo.InlineInfo.InlineFunctionBodyInfo( inlineFunctionBodyFile.virtualFile, inlineFunctionBodyLine)) - val kotlin2 = parseStrata(smapData.kotlin2, line, project, true) + val kotlin2 = parseStrata(smapData.kotlin2, line, project, true, searchScope) if (kotlin2 != null) { inlineInfo.add(InlineFunctionHyperLinkInfo.InlineInfo.CallSiteInfo( kotlin2.first.virtualFile, @@ -137,55 +102,6 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter return InlineFunctionHyperLinkInfo(project, inlineInfo) } - private fun parseStrata(strata: String?, line: Int, project: Project, isKotlin2: Boolean): Pair? { - if (strata == null) return null - - val mappings = SMAPParser.parse(strata) - - val mappingInfo = mappings.fileMappings.firstOrNull { - it.getIntervalIfContains(line) != null - } ?: return null - - val newJvmName = JvmClassName.byInternalName(mappingInfo.path) - val newSourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, newJvmName, mappingInfo.name) ?: return null - - val interval = mappingInfo.getIntervalIfContains(line)!! - return newSourceFile to (if (isKotlin2) interval.source else interval.mapDestToSource(line)) - 1 - } - - private fun readDebugInfo(bytes: ByteArray): SmapData? { - val cr = ClassReader(bytes) - var debugInfo: String? = null - cr.accept(object : ClassVisitor(InlineCodegenUtil.API) { - override fun visitSource(source: String?, debug: String?) { - debugInfo = debug - } - }, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE) - return debugInfo?.let { SmapData(it) } - } - - private class SmapData(debugInfo: String) { - var kotlin1: String? = null - private set - var kotlin2: String? = null - private set - - init { - val intervals = debugInfo.split(SMAP.END).filter { it.isNotBlank() } - when(intervals.count()) { - 1 -> { - kotlin1 = intervals[0] + SMAP.END - } - else -> { - kotlin1 = intervals[0] + SMAP.END - kotlin2 = intervals[1] + SMAP.END - } - } - } - } - - private fun FileMapping.getIntervalIfContains(destLine: Int) = lineMappings.firstOrNull { it.contains(destLine) } - private fun patchResult(result: Filter.Result, line: String): Filter.Result { val newHyperlinkInfo = createHyperlinkInfo(line) ?: return result