Debugger: Do not ignore duplicated locations for line breakpoints
This commit is contained in:
+1
-3
@@ -7,21 +7,19 @@ package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
|
||||
class KotlinLineBreakpoint(
|
||||
project: Project?,
|
||||
xBreakpoint: XBreakpoint<out XBreakpointProperties<*>>?
|
||||
) : LineBreakpoint<JavaLineBreakpointProperties>(project, xBreakpoint) {
|
||||
) : KotlinLineBreakpointBase(project, xBreakpoint) {
|
||||
override fun processClassPrepare(debugProcess: DebugProcess?, classType: ReferenceType?) {
|
||||
val sourcePosition = xBreakpoint?.sourcePosition
|
||||
|
||||
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints;
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.debugger.SourcePosition;
|
||||
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||
import com.intellij.debugger.engine.requests.RequestManagerImpl;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.debugger.ui.breakpoints.FilteredRequestor;
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectFileIndex;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.search.EverythingGlobalScope;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||
import com.sun.jdi.ClassNotPreparedException;
|
||||
import com.sun.jdi.Location;
|
||||
import com.sun.jdi.ObjectCollectedException;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import com.sun.jdi.request.BreakpointRequest;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
|
||||
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
// Copied from com.intellij.debugger.ui.breakpoints.LineBreakpoint.
|
||||
// Changed parts are marked with '// MODIFICATION: ' comments.
|
||||
// This should be deleted when IDEA replaces the 'MethodBytecodeUtil.removeSameLineLocations' with overload-able method call.
|
||||
public abstract class KotlinLineBreakpointBase extends LineBreakpoint<JavaLineBreakpointProperties> {
|
||||
private static final Logger LOG = Logger.getInstance(KotlinLineBreakpointBase.class);
|
||||
|
||||
protected KotlinLineBreakpointBase(Project project, XBreakpoint xBreakpoint) {
|
||||
super(project, xBreakpoint);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static BreakpointRequest createLocationBreakpointRequest(@NotNull FilteredRequestor requestor,
|
||||
@Nullable Location location,
|
||||
@NotNull DebugProcessImpl debugProcess) {
|
||||
if (location != null) {
|
||||
RequestManagerImpl requestsManager = debugProcess.getRequestsManager();
|
||||
BreakpointRequest request = requestsManager.createBreakpointRequest(requestor, location);
|
||||
requestsManager.enableRequest(request);
|
||||
return request;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createRequestForPreparedClass(final DebugProcessImpl debugProcess, final ReferenceType classType) {
|
||||
if (!ReadAction.compute(() -> isInScopeOf(debugProcess, classType.name()))) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(classType.name() + " is out of debug-process scope, breakpoint request won't be created for line " + getLineIndex());
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
List<Location> locations = debugProcess.getPositionManager().locationsOfLine(classType, getSourcePosition());
|
||||
if (!locations.isEmpty()) {
|
||||
locations = StreamEx.of(locations).peek(loc -> {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Found location [codeIndex=" + loc.codeIndex() +
|
||||
"] for reference type " + classType.name() +
|
||||
" at line " + getLineIndex() +
|
||||
"; isObsolete: " + (debugProcess.getVirtualMachineProxy().versionHigher("1.4") && loc.method().isObsolete()));
|
||||
}
|
||||
}).filter(l -> acceptLocation(debugProcess, classType, l)).toList();
|
||||
// MODIFICATION: Start Kotlin implementation
|
||||
//locations = MethodBytecodeUtil.removeSameLineLocations(locations);
|
||||
// MODIFICATION: End Kotlin implementation
|
||||
for (Location loc : locations) {
|
||||
createLocationBreakpointRequest(this, loc, debugProcess);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Created breakpoint request for reference type " + classType.name() + " at line " + getLineIndex() + "; codeIndex=" + loc.codeIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (DebuggerUtilsEx.allLineLocations(classType) == null) {
|
||||
// there's no line info in this class
|
||||
debugProcess.getRequestsManager()
|
||||
.setInvalid(this, DebuggerBundle.message("error.invalid.breakpoint.no.line.info", classType.name()));
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("No line number info in " + classType.name());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// there's no executable code in this class
|
||||
debugProcess.getRequestsManager().setInvalid(this, DebuggerBundle.message(
|
||||
"error.invalid.breakpoint.no.executable.code", (getLineIndex() + 1), classType.name())
|
||||
);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("No locations of type " + classType.name() + " found at line " + getLineIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ClassNotPreparedException ex) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("ClassNotPreparedException: " + ex.getMessage());
|
||||
}
|
||||
// there's a chance to add a breakpoint when the class is prepared
|
||||
}
|
||||
catch (ObjectCollectedException ex) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("ObjectCollectedException: " + ex.getMessage());
|
||||
}
|
||||
// there's a chance to add a breakpoint when the class is prepared
|
||||
}
|
||||
catch(Exception ex) {
|
||||
LOG.info(ex);
|
||||
}
|
||||
updateUI();
|
||||
}
|
||||
|
||||
private boolean isInScopeOf(DebugProcessImpl debugProcess, String className) {
|
||||
final SourcePosition position = getSourcePosition();
|
||||
if (position != null) {
|
||||
final VirtualFile breakpointFile = position.getFile().getVirtualFile();
|
||||
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
|
||||
if (breakpointFile != null && fileIndex.isUnderSourceRootOfType(breakpointFile, JavaModuleSourceRootTypes.SOURCES)) {
|
||||
if (debugProcess.getSearchScope().contains(breakpointFile)) {
|
||||
return true;
|
||||
}
|
||||
// apply filtering to breakpoints from content sources only, not for sources attached to libraries
|
||||
final Collection<VirtualFile> candidates = findClassCandidatesInSourceContent(className, debugProcess.getSearchScope(), fileIndex);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Found "+ (candidates == null? "null" : candidates.size()) + " candidate containing files for class " + className);
|
||||
}
|
||||
if (candidates == null) {
|
||||
// If no candidates are found in scope then assume that class is loaded dynamically and allow breakpoint
|
||||
return true;
|
||||
}
|
||||
|
||||
// breakpointFile is not in scope here and there are some candidates in scope
|
||||
//for (VirtualFile classFile : candidates) {
|
||||
// if (LOG.isDebugEnabled()) {
|
||||
// LOG.debug("Breakpoint file: " + breakpointFile.getPath()+ "; candidate file: " + classFile.getPath());
|
||||
// }
|
||||
// if (breakpointFile.equals(classFile)) {
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
final GlobalSearchScope scope = debugProcess.getSearchScope();
|
||||
final boolean contains = scope.contains(breakpointFile);
|
||||
List<VirtualFile> files = ContainerUtil.map(
|
||||
JavaPsiFacade.getInstance(myProject).findClasses(className, scope),
|
||||
aClass -> aClass.getContainingFile().getVirtualFile());
|
||||
List<VirtualFile> allFiles = ContainerUtil.map(
|
||||
JavaPsiFacade.getInstance(myProject).findClasses(className, new EverythingGlobalScope(myProject)),
|
||||
aClass -> aClass.getContainingFile().getVirtualFile());
|
||||
final VirtualFile contentRoot = fileIndex.getContentRootForFile(breakpointFile);
|
||||
final Module module = fileIndex.getModuleForFile(breakpointFile);
|
||||
|
||||
LOG.debug("Did not find '" +
|
||||
className + "' in " + scope +
|
||||
"; contains=" + contains +
|
||||
"; contentRoot=" + contentRoot +
|
||||
"; module = " + module +
|
||||
"; all files in index are: " + files+
|
||||
"; all possible files are: " + allFiles
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Collection<VirtualFile> findClassCandidatesInSourceContent(final String className, final GlobalSearchScope scope, final ProjectFileIndex fileIndex) {
|
||||
final int dollarIndex = className.indexOf("$");
|
||||
final String topLevelClassName = dollarIndex >= 0? className.substring(0, dollarIndex) : className;
|
||||
return ReadAction.compute(() -> {
|
||||
final PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(topLevelClassName, scope);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Found "+ classes.length + " classes " + topLevelClassName + " in scope "+scope);
|
||||
}
|
||||
if (classes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
final List<VirtualFile> list = new ArrayList<>(classes.length);
|
||||
for (PsiClass aClass : classes) {
|
||||
final PsiFile psiFile = aClass.getContainingFile();
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
final StringBuilder msg = new StringBuilder();
|
||||
msg.append("Checking class ").append(aClass.getQualifiedName());
|
||||
msg.append("\n\t").append("PsiFile=").append(psiFile);
|
||||
if (psiFile != null) {
|
||||
final VirtualFile vFile = psiFile.getVirtualFile();
|
||||
msg.append("\n\t").append("VirtualFile=").append(vFile);
|
||||
if (vFile != null) {
|
||||
msg.append("\n\t").append("isInSourceContent=").append(fileIndex.isUnderSourceRootOfType(vFile, JavaModuleSourceRootTypes.SOURCES));
|
||||
}
|
||||
}
|
||||
LOG.debug(msg.toString());
|
||||
}
|
||||
|
||||
if (psiFile == null) {
|
||||
return null;
|
||||
}
|
||||
final VirtualFile vFile = psiFile.getVirtualFile();
|
||||
if (vFile == null || !fileIndex.isUnderSourceRootOfType(vFile, JavaModuleSourceRootTypes.SOURCES)) {
|
||||
return null; // this will switch off the check if at least one class is from libraries
|
||||
}
|
||||
list.add(vFile);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,4 +18,4 @@ inline fun inlineCall(l: () -> Unit) {
|
||||
l()
|
||||
}
|
||||
|
||||
// STEP_OVER: 3
|
||||
// STEP_OVER: 4
|
||||
Vendored
+1
@@ -2,6 +2,7 @@ LineBreakpoint created at stepOverInlineFunWithRecursionCall.kt:6
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
stepOverInlineFunWithRecursionCall.kt:6
|
||||
stepOverInlineFunWithRecursionCall.kt:6
|
||||
stepOverInlineFunWithRecursionCall.kt:9
|
||||
stepOverInlineFunWithRecursionCall.kt:14
|
||||
stepOverInlineFunWithRecursionCall.kt:15
|
||||
|
||||
Reference in New Issue
Block a user