Debugger: fix breakpoints in files with JvmName annotation

Drop unnessecary logic about part classes with hashcodes
#KT-9480 Fixed
This commit is contained in:
Natalia Ukhorskaya
2015-10-07 16:05:35 +03:00
parent 11ea5f28e8
commit 7e356354e0
7 changed files with 58 additions and 222 deletions
@@ -41,6 +41,8 @@ import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.JetTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
import org.jetbrains.kotlin.fileClasses.getFileClassInternalName
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
@@ -50,10 +52,8 @@ import org.jetbrains.kotlin.idea.util.DebuggerUtils
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
@@ -80,7 +80,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
val javaClassName = JvmClassName.byInternalName(defaultInternalName(location))
val project = myDebugProcess.project
val defaultPsiFile = DebuggerUtils.findSourceFileForClass(project, GlobalSearchScope.allScope(project), javaClassName, javaSourceFileName, 0)
val defaultPsiFile = DebuggerUtils.findSourceFileForClass(project, GlobalSearchScope.allScope(project), javaClassName, javaSourceFileName)
if (defaultPsiFile != null) {
return SourcePosition.createFromLine(defaultPsiFile, 0)
}
@@ -178,7 +178,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
val project = myDebugProcess.project
return DebuggerUtils.findSourceFileForClass(project, GlobalSearchScope.allScope(project), className, sourceName, location.lineNumber() - 1)
return DebuggerUtils.findSourceFileForClass(project, GlobalSearchScope.allScope(project), className, sourceName)
}
private fun defaultInternalName(location: Location): String {
@@ -375,19 +375,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
}
}
if (isInLibrary) {
val elementAtForLibraryFile =
if (element is JetDeclaration) element
else PsiTreeUtil.getParentOfType(element, JetDeclaration::class.java)
assert(elementAtForLibraryFile != null) {
"Couldn't find element at breakpoint for library file " + file.name +
(if (notPositionedElement == null) "" else ", notPositionedElement = " + notPositionedElement.getElementTextWithContext())
}
return PositionedElement(findPackagePartInternalNameForLibraryFile(elementAtForLibraryFile!!), elementAtForLibraryFile)
}
return PositionedElement(PackagePartClassUtils.getPackagePartInternalName(file), element)
return PositionedElement(NoResolveFileClassesProvider.getFileClassInternalName(file), element)
}
private val TYPES_TO_CALCULATE_CLASSNAME: Array<Class<out JetElement>> =
@@ -439,7 +427,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
return InlineUtil.isInlinedArgument(functionLiteral, context, false)
}
private fun createKeyForTypeMapper(file: JetFile) = PackagePartClassUtils.getPackagePartInternalName(file)
private fun createKeyForTypeMapper(file: JetFile) = NoResolveFileClassesProvider.getFileClassInternalName(file)
}
private fun findInlinedCalls(element: PsiElement?, jetFile: PsiFile?): List<String> {
@@ -1,129 +0,0 @@
/*
* Copyright 2010-2015 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.debugger
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.impl.scopes.JdkScope
import com.intellij.openapi.module.impl.scopes.LibraryScope
import com.intellij.openapi.roots.JdkOrderEntry
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.libraries.LibraryUtil
import com.intellij.psi.search.FilenameIndex
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
import org.jetbrains.kotlin.psi.JetDeclaration
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor
private val LOG = Logger.getInstance("org.jetbrains.kotlin.idea.debugger")
/**
* This method finds class name for top-level declaration in source file attached to library.
* The problem is that package-part class file has hash in className and it depends on machine where library was built,
* so we couldn't predict it.
* 1. find all .class files with package-part prefix, if there is one - return it
* 2. find all descriptors with same signature, if there is one - return it
* 3. else -> return null, because it means that there is more than one function with same signature in project
*/
fun findPackagePartInternalNameForLibraryFile(topLevelDeclaration: JetDeclaration): String? {
val packagePartFile = findPackagePartFileNamesForElement(topLevelDeclaration).singleOrNull()
if (packagePartFile != null) return packagePartFile
val descriptor = topLevelDeclaration.resolveToDescriptor()
if (descriptor !is CallableDescriptor) return null
val packageFqName = topLevelDeclaration.getContainingJetFile().getPackageFqName()
val packageDescriptor = descriptor.module.getPackage(packageFqName)
val descFromSourceText = render(descriptor)
val descriptors: Collection<CallableDescriptor> = when (descriptor) {
is FunctionDescriptor -> packageDescriptor.memberScope.getFunctions(descriptor.name, NoLookupLocation.FROM_IDE)
is PropertyDescriptor -> packageDescriptor.memberScope.getProperties(descriptor.name, NoLookupLocation.FROM_IDE)
else -> {
reportError(topLevelDeclaration, descriptor)
listOf()
}
}
val deserializedDescriptor = descriptors
.filterIsInstance<DeserializedCallableMemberDescriptor>()
.filter { render(it) == descFromSourceText }
.singleOrNull()
if (deserializedDescriptor == null) {
reportError(topLevelDeclaration, descriptor)
return null
}
val implClassName = JvmFileClassUtil.getImplClassName(deserializedDescriptor)
if (implClassName != null) {
return JvmClassName.byFqNameWithoutInnerClasses(packageFqName.child(implClassName)).getInternalName()
}
return null
}
private fun findPackagePartFileNamesForElement(elementAt: JetElement): List<String> {
val project = elementAt.getProject()
val file = elementAt.getContainingJetFile()
val packagePartName = PackagePartClassUtils.getPackagePartFqName(file).shortName().asString()
val packagePartNameWoHash = packagePartName.substring(0, packagePartName.lastIndexOf("$") + 1)
val libraryEntry = LibraryUtil.findLibraryEntry(file.getVirtualFile(), project)
val scope = if (libraryEntry is LibraryOrderEntry){
LibraryScope(project, libraryEntry.getLibrary() ?: throw AssertionError("Cannot find library for file ${file.getVirtualFile()?.getPath()}"))
}
else {
JdkScope(project, libraryEntry as JdkOrderEntry)
}
val packagePartFiles = FilenameIndex.getAllFilenames(project).asSequence().filter {
it.startsWith(packagePartNameWoHash) && it.endsWith(".class") &&
!it.substringAfter(packagePartNameWoHash).contains("\$")
}.flatMap {
FilenameIndex.getVirtualFilesByName(project, it, scope).asSequence()
}.map {
val packageFqName = file.getPackageFqName()
if (packageFqName.isRoot()) {
it.getNameWithoutExtension()
} else {
"${packageFqName.asString()}.${it.getNameWithoutExtension()}"
}
}
return packagePartFiles.toList()
}
private fun render(desc: DeclarationDescriptor) = DescriptorRenderer.FQ_NAMES_IN_TYPES.render(desc)
private fun reportError(element: JetElement, descriptor: CallableDescriptor?) {
LOG.error("Couldn't calculate class name for element in library scope:\n" +
element.getElementTextWithContext() +
if (descriptor != null) "\ndescriptor = ${render(descriptor)}" else ""
)
}
@@ -63,7 +63,7 @@ public class JetExceptionFilter implements Filter {
String internalName = fullyQualifiedName.replace('.', '/');
JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
JetFile file = DebuggerUtils.findSourceFileForClass(project, searchScope, jvmClassName, fileName, lineNumber);
JetFile file = DebuggerUtils.findSourceFileForClass(project, searchScope, jvmClassName, fileName);
if (file == null) return null;
VirtualFile virtualFile = file.getVirtualFile();
@@ -22,11 +22,9 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.libraries.LibraryUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import kotlin.KotlinPackage;
import kotlin.Pair;
import kotlin.jvm.functions.Function1;
@@ -35,13 +33,10 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils;
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde;
import org.jetbrains.kotlin.idea.debugger.DebuggerPackage;
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade;
import org.jetbrains.kotlin.idea.stubindex.StaticFacadeIndexUtil;
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
@@ -66,9 +61,8 @@ public class DebuggerUtils {
public static JetFile findSourceFileForClass(
@NotNull Project project,
@NotNull GlobalSearchScope searchScope,
@NotNull final JvmClassName className,
@NotNull final String fileName,
final int lineNumber
@NotNull JvmClassName className,
@NotNull final String fileName
) {
String extension = FileUtilRt.getExtension(fileName);
if (!KOTLIN_EXTENSIONS.contains(extension)) return null;
@@ -113,66 +107,15 @@ public class DebuggerUtils {
}
}
if (isPackagePartClassName(className)) {
JetFile file = getFileForPackagePartPrefixedName(filesWithExactName, className.getInternalName());
if (file != null) {
return file;
}
boolean isInLibrary = KotlinPackage.all(filesWithExactName, new Function1<JetFile, Boolean>() {
@Override
public Boolean invoke(JetFile file) {
return LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null;
}
});
if (isInLibrary) {
return KotlinPackage.singleOrNull(KotlinPackage.filter(filesWithExactName, new Function1<JetFile, Boolean>() {
@Override
public Boolean invoke(JetFile file) {
Integer startLineOffset = CodeInsightUtils.getStartLineOffset(file, lineNumber);
assert startLineOffset != null : "Cannot find start line offset for file " + file.getName() + ", line " +
lineNumber;
JetDeclaration elementAt = PsiTreeUtil.getParentOfType(file.findElementAt(startLineOffset), JetDeclaration.class);
return elementAt != null &&
className.getInternalName().equals(DebuggerPackage.findPackagePartInternalNameForLibraryFile(elementAt));
}
}));
}
return null;
}
return filesWithExactName.iterator().next();
}
private static boolean isPackagePartClassName(JvmClassName className) {
String packageName = PackageClassUtils.getPackageClassInternalName(className.getPackageFqName());
String internalName = className.getInternalName();
return !internalName.equals(packageName) && internalName.startsWith(packageName);
}
private static boolean isPackageClassName(JvmClassName className) {
String packageName = PackageClassUtils.getPackageClassInternalName(className.getPackageFqName());
return packageName.equals(className.getInternalName());
}
@Nullable
private static JetFile getFileForPackagePartPrefixedName(
@NotNull Collection<JetFile> allPackageFiles,
@NotNull String classInternalName
) {
for (JetFile file : allPackageFiles) {
String packagePartInternalName = PackagePartClassUtils.getPackagePartInternalName(file);
if (classInternalName.startsWith(packagePartInternalName)) {
return file;
}
}
return null;
}
@NotNull
public static Pair<BindingContext, List<JetFile>> analyzeInlinedFunctions(
@NotNull ResolutionFacade resolutionFacadeForFile,
@@ -0,0 +1,6 @@
@file:JvmName("ABC")
package a
fun foo() {
"" // a/ABC
}
@@ -28,23 +28,27 @@ import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
import kotlin.KotlinPackage;
import kotlin.SequencesKt;
import kotlin.StringsKt;
import kotlin.Unit;
import kotlin.io.FilesKt;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
import org.jetbrains.kotlin.codegen.GenerationUtils;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase;
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase;
import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider;
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase;
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor;
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase;
import org.jetbrains.kotlin.psi.JetFile;
import org.jetbrains.kotlin.utils.UtilsPackage;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -52,7 +56,7 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class AbstractJetPositionManagerTest extends KotlinMultiFileTestCase {
public abstract class AbstractJetPositionManagerTest extends JetLightCodeInsightFixtureTestCase {
// Breakpoint is given as a line comment on a specific line, containing the regexp to match the name of the class where that line
// can be found. This pattern matches against these line comments and saves the class name in the first group
private static final Pattern BREAKPOINT_PATTERN = Pattern.compile("^.*//\\s*(.+)\\s*$");
@@ -60,15 +64,21 @@ public abstract class AbstractJetPositionManagerTest extends KotlinMultiFileTest
@NotNull
@Override
protected String getTestDataPath() {
return PluginTestCaseBase.getTestDataPathBase();
return PluginTestCaseBase.getTestDataPathBase() + "/debugger/positionManager/";
}
@Override
public void setUp() {
super.setUp();
myFixture.setTestDataPath(PluginTestCaseBase.getTestDataPathBase());
}
private DebugProcessImpl debugProcess;
@NotNull
@Override
protected String getTestRoot() {
return "/debugger/positionManager/";
protected LightProjectDescriptor getProjectDescriptor() {
return JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE;
}
@NotNull
@@ -89,18 +99,30 @@ public abstract class AbstractJetPositionManagerTest extends KotlinMultiFileTest
protected void doTest(@NotNull String fileName) throws Exception {
if (fileName.endsWith(".kt")) {
String path = KotlinPackage.substringAfter(fileName, PluginTestCaseBase.TEST_DATA_PROJECT_RELATIVE.substring(1), fileName);
configureByFile(path);
String path = getPath(fileName);
myFixture.configureByFile(path);
}
else {
VirtualFile rootDir = PsiTestUtil.createTestProjectStructure(myProject, myModule, fileName, myFilesToDelete, false);
prepareProject(rootDir);
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
String path = getPath(fileName);
SequencesKt.forEach(FilesKt.walkTopDown(new File(path)), new Function1<File, Unit>() {
@Override
public Unit invoke(File file) {
String fileName = file.getName();
String path = getPath(fileName);
myFixture.configureByFile(path);
return null;
}
});
}
performTest();
}
@NotNull
private static String getPath(@NotNull String fileName) {
return StringsKt.substringAfter(fileName, PluginTestCaseBase.TEST_DATA_PROJECT_RELATIVE.substring(1), fileName);
}
private void performTest() {
Project project = getProject();
List<JetFile> files = new ArrayList<JetFile>(PluginJetFilesProvider.allFilesInProject(project));
@@ -91,6 +91,12 @@ public class JetPositionManagerTestGenerated extends AbstractJetPositionManagerT
doTest(fileName);
}
@TestMetadata("JvmNameAnnotation.kt")
public void testJvmNameAnnotation() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/positionManager/JvmNameAnnotation.kt");
doTest(fileName);
}
@TestMetadata("localFunction.kt")
public void testLocalFunction() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/positionManager/localFunction.kt");