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.engine.DebugProcess
|
||||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.xdebugger.XSourcePosition
|
import com.intellij.xdebugger.XSourcePosition
|
||||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||||
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
|
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
|
||||||
import com.sun.jdi.AbsentInformationException
|
import com.sun.jdi.AbsentInformationException
|
||||||
import com.sun.jdi.ReferenceType
|
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.codegen.inline.KOTLIN_STRATA_NAME
|
||||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||||
|
|
||||||
class KotlinLineBreakpoint(
|
class KotlinLineBreakpoint(
|
||||||
project: Project?,
|
project: Project?,
|
||||||
xBreakpoint: XBreakpoint<out XBreakpointProperties<*>>?
|
xBreakpoint: XBreakpoint<out XBreakpointProperties<*>>?
|
||||||
) : LineBreakpoint<JavaLineBreakpointProperties>(project, xBreakpoint) {
|
) : KotlinLineBreakpointBase(project, xBreakpoint) {
|
||||||
override fun processClassPrepare(debugProcess: DebugProcess?, classType: ReferenceType?) {
|
override fun processClassPrepare(debugProcess: DebugProcess?, classType: ReferenceType?) {
|
||||||
val sourcePosition = xBreakpoint?.sourcePosition
|
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()
|
l()
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP_OVER: 3
|
// STEP_OVER: 4
|
||||||
Vendored
+1
@@ -2,6 +2,7 @@ LineBreakpoint created at stepOverInlineFunWithRecursionCall.kt:6
|
|||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
stepOverInlineFunWithRecursionCall.kt:6
|
stepOverInlineFunWithRecursionCall.kt:6
|
||||||
|
stepOverInlineFunWithRecursionCall.kt:6
|
||||||
stepOverInlineFunWithRecursionCall.kt:9
|
stepOverInlineFunWithRecursionCall.kt:9
|
||||||
stepOverInlineFunWithRecursionCall.kt:14
|
stepOverInlineFunWithRecursionCall.kt:14
|
||||||
stepOverInlineFunWithRecursionCall.kt:15
|
stepOverInlineFunWithRecursionCall.kt:15
|
||||||
|
|||||||
Reference in New Issue
Block a user