Debugger: Add Kotlin Function breakpoints (KT-22116)

Now it's possible to put a function breakpoint.
In JVM, function breakpoints behave as JVM method breakpoints. Normally, they're triggered twice – once on enter, and once on exit.
This commit is contained in:
Yan Zhulanow
2019-07-03 18:25:54 +09:00
parent 55ea0840b1
commit 22c18ffaa9
8 changed files with 920 additions and 5 deletions
@@ -242,6 +242,7 @@ kotlin.compiler.js.option.output.copy.dir=O&utput directory for library &runtime
debugger.filter.ignore.internal.classes=Do not step into Kotlin runtime library implementation classes
debugger.data.view.delegated.properties=Calculate values of delegated properties (may affect program execution)
debugger.field.watchpoints.tab.title=Kotlin Field Watchpoints
debugger.function.breakpoints.tab.title=Kotlin Function Breakpoints
debugger.field.watchpoints.properties.panel.field.access.label=Field &access
debugger.field.watchpoints.properties.panel.field.modification.label=Field &modification
debugger.field.watchpoints.properties.panel.field.initialization.label=Field &initialization
@@ -26,11 +26,18 @@ class KotlinFieldBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
}
}
class KotlinLineBreakpointHandlerFactory: JavaBreakpointHandlerFactory {
class KotlinLineBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
return KotlinLineBreakpointHandler(process)
}
}
class KotlinFunctionBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
return KotlinFunctionBreakpointHandler(process)
}
}
class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinFieldBreakpointType::class.java, process)
class KotlinLineBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinLineBreakpointType::class.java, process)
class KotlinLineBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinLineBreakpointType::class.java, process)
class KotlinFunctionBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinFunctionBreakpointType::class.java, process)
@@ -0,0 +1,667 @@
/*
* 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.DebuggerManagerEx;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.*;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.debugger.engine.requests.RequestManagerImpl;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.impl.DebuggerUtilsImpl;
import com.intellij.debugger.impl.PositionUtil;
import com.intellij.debugger.jdi.ClassesByNameProvider;
import com.intellij.debugger.jdi.MethodBytecodeUtil;
import com.intellij.debugger.requests.Requestor;
import com.intellij.debugger.ui.breakpoints.*;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.ProgressWindow;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizerUtil;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.ui.LayeredIcon;
import com.intellij.util.DocumentUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XBreakpointListener;
import com.intellij.xdebugger.impl.XDebugSessionImpl;
import com.sun.jdi.*;
import com.sun.jdi.event.LocatableEvent;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.event.MethodExitEvent;
import com.sun.jdi.request.*;
import one.util.streamex.StreamEx;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.debugger.breakpoints.properties.JavaMethodBreakpointProperties;
import org.jetbrains.kotlin.asJava.LightClassUtilsKt;
import org.jetbrains.kotlin.psi.KtClass;
import org.jetbrains.kotlin.psi.KtDeclaration;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
import javax.swing.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
// This class is copied from com.intellij.debugger.ui.breakpoints.MethodBreakpoint.
// Changed parts are marked with '// MODIFICATION: ' comments.
// This should be deleted when IDEA opens the method breakpoint API (presumably in 193).
public class KotlinFunctionBreakpoint extends BreakpointWithHighlighter<JavaMethodBreakpointProperties> implements MethodBreakpointBase {
private static final Logger LOG = Logger.getInstance(KotlinFunctionBreakpoint.class);
@Nullable private JVMName mySignature;
private boolean myIsStatic;
protected KotlinFunctionBreakpoint(@NotNull Project project, XBreakpoint breakpoint) {
super(project, breakpoint);
}
public boolean isStatic() {
return myIsStatic;
}
@Override
@NotNull
public Key<MethodBreakpoint> getCategory() {
return MethodBreakpoint.CATEGORY;
}
@Override
public boolean isValid() {
return super.isValid() && getMethodName() != null;
}
@Override
public void reload() {
super.reload();
setMethodName(null);
mySignature = null;
SourcePosition sourcePosition = getSourcePosition();
if (sourcePosition != null) {
MethodDescriptor descriptor = getMethodDescriptor(myProject, sourcePosition);
if (descriptor != null) {
setMethodName(descriptor.methodName);
mySignature = descriptor.methodSignature;
myIsStatic = descriptor.isStatic;
}
}
PsiClass psiClass = getPsiClass();
if (psiClass != null) {
getProperties().myClassPattern = psiClass.getQualifiedName();
}
if (myIsStatic) {
setInstanceFiltersEnabled(false);
}
}
private static void createRequestForSubClasses(@NotNull MethodBreakpointBase breakpoint,
@NotNull DebugProcessImpl debugProcess,
@NotNull ReferenceType baseType) {
DebuggerManagerThreadImpl.assertIsManagerThread();
RequestManagerImpl requestsManager = debugProcess.getRequestsManager();
ClassPrepareRequest request = requestsManager.createClassPrepareRequest((debuggerProcess, referenceType) -> {
if (DebuggerUtilsImpl.instanceOf(referenceType, baseType)) {
createRequestForPreparedClassEmulated(breakpoint, debugProcess, referenceType, false);
}
}, null);
if (request != null) {
requestsManager.registerRequest(breakpoint, request);
request.enable();
}
AtomicReference<ProgressWindow> indicatorRef = new AtomicReference<>();
ApplicationManager.getApplication().invokeAndWait(
() -> {
ProgressWindow progress =
new ProgressWindow(true, false, debugProcess.getProject(), "Cancel emulation");
progress.setDelayInMillis(2000);
indicatorRef.set(progress);
});
ProgressWindow indicator = indicatorRef.get();
AtomicBoolean changed = new AtomicBoolean();
XBreakpointListener<XBreakpoint<?>> listener = new XBreakpointListener<XBreakpoint<?>>() {
void changed(@NotNull XBreakpoint b) {
if (b == breakpoint.getXBreakpoint()) {
changed.set(true);
indicator.cancel();
}
}
@Override
public void breakpointRemoved(@NotNull XBreakpoint b) {
changed(b);
}
@Override
public void breakpointChanged(@NotNull XBreakpoint b) {
changed(b);
}
};
debugProcess.getProject().getMessageBus().connect(indicator).subscribe(XBreakpointListener.TOPIC, listener);
ProgressManager.getInstance().executeProcessUnderProgress(
() -> processPreparedSubTypes(baseType,
(subType, classesByName) ->
createRequestForPreparedClassEmulated(breakpoint, debugProcess, subType, classesByName, false),
indicator),
indicator);
if (indicator.isCanceled() && !changed.get()) {
breakpoint.disableEmulation();
}
}
@Override
public void disableEmulation() {
MethodBreakpointBase.disableEmulation(this);
}
private static void createRequestForPreparedClassEmulated(
@NotNull MethodBreakpointBase breakpoint,
@NotNull DebugProcessImpl debugProcess,
@NotNull ReferenceType classType,
boolean base
) {
createRequestForPreparedClassEmulated(breakpoint, debugProcess, classType, debugProcess.getVirtualMachineProxy().getClassesByNameProvider(), base);
}
private static boolean shouldCreateRequest(
Requestor requestor,
XBreakpoint xBreakpoint,
DebugProcessImpl debugProcess,
boolean forPreparedClass
) {
return ReadAction.compute(() -> {
JavaDebugProcess process = debugProcess.getXdebugProcess();
return process != null
&& debugProcess.isAttached()
&& (xBreakpoint == null || ((XDebugSessionImpl)process.getSession()).isBreakpointActive(xBreakpoint))
&& (forPreparedClass || debugProcess.getRequestsManager().findRequests(requestor).isEmpty());
});
}
private static void createRequestForPreparedClassEmulated(
@NotNull MethodBreakpointBase breakpoint,
@NotNull DebugProcessImpl debugProcess,
@NotNull ReferenceType classType,
@NotNull ClassesByNameProvider classesByName,
boolean base
) {
if (!MethodBreakpointBase.canBeEmulated(debugProcess)) {
breakpoint.disableEmulation();
return;
}
if (!base && !shouldCreateRequest(breakpoint, breakpoint.getXBreakpoint(), debugProcess, true)) {
return;
}
Method lambdaMethod = MethodBytecodeUtil.getLambdaMethod(classType, classesByName);
if (lambdaMethod != null &&
!breakpoint
.matchingMethods(StreamEx.of(((ClassType)classType).interfaces()).flatCollection(ReferenceType::allMethods), debugProcess)
.findFirst().isPresent()) {
return;
}
StreamEx<Method> methods = lambdaMethod != null
? StreamEx.of(lambdaMethod)
: breakpoint.matchingMethods(StreamEx.of(classType.methods()).filter(m -> base || !m.isAbstract()), debugProcess);
boolean found = false;
for (Method method : methods) {
found = true;
if (method.isNative()) {
breakpoint.disableEmulation();
return;
}
Method target = MethodBytecodeUtil.getBridgeTargetMethod(method, classesByName);
if (target != null && !ContainerUtil.isEmpty(DebuggerUtilsEx.allLineLocations(target))) {
method = target;
}
List<Location> allLineLocations = DebuggerUtilsEx.allLineLocations(method);
if (allLineLocations == null && !method.isBridge()) { // no line numbers
breakpoint.disableEmulation();
return;
}
if (!ContainerUtil.isEmpty(allLineLocations)) {
if (breakpoint.isWatchEntry()) {
createLocationBreakpointRequest(breakpoint, ContainerUtil.getFirstItem(allLineLocations), debugProcess, true);
}
if (breakpoint.isWatchExit()) {
MethodBytecodeUtil.visit(method, new MethodVisitor(Opcodes.API_VERSION) {
int myLastLine = 0;
@Override
public void visitLineNumber(int line, Label start) {
myLastLine = line;
}
@Override
public void visitInsn(int opcode) {
switch (opcode) {
case Opcodes.RETURN:
case Opcodes.IRETURN:
case Opcodes.FRETURN:
case Opcodes.ARETURN:
case Opcodes.LRETURN:
case Opcodes.DRETURN:
//case Opcodes.ATHROW:
allLineLocations.stream()
.filter(l -> l.lineNumber() == myLastLine)
.findFirst().ifPresent(location -> createLocationBreakpointRequest(breakpoint, location, debugProcess, false));
}
}
}, true);
}
}
}
if (base && found) {
// desired class found - now also track all new classes
createRequestForSubClasses(breakpoint, debugProcess, classType);
}
}
private static void createLocationBreakpointRequest(@NotNull FilteredRequestor requestor,
@Nullable Location location,
@NotNull DebugProcessImpl debugProcess,
boolean methodEntry) {
BreakpointRequest request = createLocationBreakpointRequest(requestor, location, debugProcess);
if (request != null) {
request.putProperty(METHOD_ENTRY_KEY, methodEntry);
}
}
@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(@NotNull DebugProcessImpl debugProcess, @NotNull ReferenceType classType) {
if (isEmulated()) {
createRequestForPreparedClassEmulated(this, debugProcess, classType, true);
}
else {
createRequestForPreparedClassOriginal(debugProcess, classType);
}
}
private void createRequestForPreparedClassOriginal(@NotNull DebugProcessImpl debugProcess, @NotNull ReferenceType classType) {
try {
boolean hasMethod = false;
for (Method method : classType.allMethods()) {
String signature = method.signature();
String name = method.name();
String nameFromProperties = getMethodName();
if (nameFromProperties != null && mySignature != null) {
if (nameFromProperties.equals(name) && mySignature.getName(debugProcess).equals(signature)) {
hasMethod = true;
break;
}
}
}
if(!hasMethod) {
debugProcess.getRequestsManager().setInvalid(
this, DebuggerBundle.message("error.invalid.breakpoint.method.not.found", classType.name())
);
return;
}
RequestManagerImpl requestManager = debugProcess.getRequestsManager();
if (isWatchEntry()) {
MethodEntryRequest entryRequest = findRequest(debugProcess, MethodEntryRequest.class, this);
if (entryRequest == null) {
entryRequest = requestManager.createMethodEntryRequest(this);
}
else {
entryRequest.disable();
}
//entryRequest.addClassFilter(myClassQualifiedName);
// use addClassFilter(ReferenceType) in order to stop on subclasses also!
entryRequest.addClassFilter(classType);
debugProcess.getRequestsManager().enableRequest(entryRequest);
}
if (isWatchExit()) {
MethodExitRequest exitRequest = findRequest(debugProcess, MethodExitRequest.class, this);
if (exitRequest == null) {
exitRequest = requestManager.createMethodExitRequest(this);
}
else {
exitRequest.disable();
}
//exitRequest.addClassFilter(myClassQualifiedName);
exitRequest.addClassFilter(classType);
debugProcess.getRequestsManager().enableRequest(exitRequest);
}
}
catch (Exception e) {
LOG.debug(e);
}
}
@Override
public String getEventMessage(@NotNull LocatableEvent event) {
return getEventMessage(event, getFileName());
}
private static String getEventMessage(@NotNull LocatableEvent event, @NotNull String defaultFileName) {
Location location = event.location();
if (event instanceof MethodEntryEvent) {
return getEventMessage(true, ((MethodEntryEvent)event).method(), location, defaultFileName);
}
if (event instanceof MethodExitEvent) {
return getEventMessage(false, ((MethodExitEvent)event).method(), location, defaultFileName);
}
Object entryProperty = event.request().getProperty(METHOD_ENTRY_KEY);
if (entryProperty instanceof Boolean) {
return getEventMessage((Boolean)entryProperty, location.method(), location, defaultFileName);
}
return "";
}
private static String getEventMessage(boolean entry, Method method, Location location, String defaultFileName) {
String locationQName = DebuggerUtilsEx.getLocationMethodQName(location);
String locationFileName = DebuggerUtilsEx.getSourceName(location, e -> defaultFileName);
int locationLine = location.lineNumber();
return DebuggerBundle.message(entry ? "status.method.entry.breakpoint.reached" : "status.method.exit.breakpoint.reached",
method.declaringType().name() + "." + method.name() + "()",
locationQName,
locationFileName,
locationLine
);
}
@Override
public PsiElement getEvaluationElement() {
return getPsiClass();
}
@Override
protected Icon getDisabledIcon(boolean isMuted) {
if (DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().findMasterBreakpoint(this) != null && isMuted) {
return AllIcons.Debugger.Db_muted_dep_method_breakpoint;
}
return null;
}
@Override
protected Icon getVerifiedIcon(boolean isMuted) {
return isSuspend() ? AllIcons.Debugger.Db_verified_method_breakpoint : AllIcons.Debugger.Db_verified_no_suspend_method_breakpoint;
}
@Override
@NotNull
protected Icon getVerifiedWarningsIcon(boolean isMuted) {
return new LayeredIcon(isMuted ? AllIcons.Debugger.Db_muted_method_breakpoint : AllIcons.Debugger.Db_method_breakpoint,
AllIcons.General.WarningDecorator);
}
@Override
public String getDisplayName() {
StringBuilder buffer = new StringBuilder();
if(isValid()) {
String className = getClassName();
boolean classNameExists = className != null && className.length() > 0;
if (classNameExists) {
buffer.append(className);
}
if(getMethodName() != null) {
if (classNameExists) {
buffer.append(".");
}
buffer.append(getMethodName());
}
}
else {
buffer.append(DebuggerBundle.message("status.breakpoint.invalid"));
}
return buffer.toString();
}
@Override
public boolean evaluateCondition(@NotNull EvaluationContextImpl context, @NotNull LocatableEvent event) throws EvaluateException {
if (!matchesEvent(event, context.getDebugProcess())) {
return false;
}
return super.evaluateCondition(context, event);
}
private boolean matchesEvent(@NotNull LocatableEvent event, DebugProcessImpl process) throws EvaluateException {
if (isEmulated()) {
return true;
}
if (getMethodName() == null || mySignature == null) {
return false;
}
Method method = event.location().method();
return method != null && method.name().equals(getMethodName()) && method.signature().equals(mySignature.getName(process));
}
@Nullable
public static KotlinFunctionBreakpoint create(@NotNull Project project, XBreakpoint xBreakpoint) {
KotlinFunctionBreakpoint breakpoint = new KotlinFunctionBreakpoint(project, xBreakpoint);
return (KotlinFunctionBreakpoint)breakpoint.init();
}
//public boolean canMoveTo(final SourcePosition position) {
// return super.canMoveTo(position) && PositionUtil.getPsiElementAt(getProject(), PsiMethod.class, position) != null;
//}
/**
* finds FQ method's class name and method's signature
*/
@Nullable
private static MethodDescriptor getMethodDescriptor(@NotNull Project project, @NotNull SourcePosition sourcePosition) {
Document document = PsiDocumentManager.getInstance(project).getDocument(sourcePosition.getFile());
if (document == null) {
return null;
}
//final int endOffset = document.getLineEndOffset(sourcePosition);
//final MethodDescriptor descriptor = docManager.commitAndRunReadAction(new Computable<MethodDescriptor>() {
// conflicts with readAction on initial breakpoints creation
MethodDescriptor descriptor = ReadAction.compute(() -> {
// MODIFICATION: Start Kotlin implementation
PsiMethod method = resolveJvmMethodFromKotlinDeclaration(project, sourcePosition);
// MODIFICATION: End Kotlin implementation
if (method == null) {
return null;
}
int methodOffset = method.getTextOffset();
if (!DocumentUtil.isValidOffset(methodOffset, document) || document.getLineNumber(methodOffset) < sourcePosition.getLine()) {
return null;
}
PsiIdentifier identifier = method.getNameIdentifier();
int methodNameOffset = identifier != null? identifier.getTextOffset() : methodOffset;
MethodDescriptor res =
new MethodDescriptor();
res.methodName = JVMNameUtil.getJVMMethodName(method);
try {
res.methodSignature = JVMNameUtil.getJVMSignature(method);
res.isStatic = method.hasModifierProperty(PsiModifier.STATIC);
}
catch (IndexNotReadyException ignored) {
return null;
}
res.methodLine = document.getLineNumber(methodNameOffset);
return res;
});
if (descriptor == null || descriptor.methodName == null || descriptor.methodSignature == null) {
return null;
}
return descriptor;
}
// MODIFICATION: Start Kotlin implementation
@Nullable
private static PsiMethod resolveJvmMethodFromKotlinDeclaration(@NotNull Project project, @NotNull SourcePosition sourcePosition) {
KtDeclaration declaration = PositionUtil.getPsiElementAt(project, KtDeclaration.class, sourcePosition);
if (declaration instanceof KtClass) {
declaration = ((KtClass) declaration).getPrimaryConstructor();
}
if (declaration == null) {
return null;
}
for (PsiElement element : LightClassUtilsKt.toLightElements(declaration)) {
if (element instanceof PsiMethod) {
// TODO handle all light methods
return (PsiMethod) element;
}
}
return null;
}
// MODIFICATION: End Kotlin implementation
@Nullable
private static <T extends EventRequest> T findRequest(
@NotNull DebugProcessImpl debugProcess,
Class<T> requestClass,
Requestor requestor
) {
return StreamEx.of(debugProcess.getRequestsManager().findRequests(requestor)).select(requestClass).findFirst().orElse(null);
}
@Override
public void readExternal(@NotNull Element breakpointNode) throws InvalidDataException {
super.readExternal(breakpointNode);
try {
getProperties().WATCH_ENTRY = Boolean.valueOf(JDOMExternalizerUtil.readField(breakpointNode, "WATCH_ENTRY"));
} catch (Exception ignored) {
}
try {
getProperties().WATCH_EXIT = Boolean.valueOf(JDOMExternalizerUtil.readField(breakpointNode, "WATCH_EXIT"));
} catch (Exception ignored) {
}
}
private boolean isEmulated() {
return getProperties().EMULATED;
}
@Override
public boolean isWatchEntry() {
return getProperties().WATCH_ENTRY;
}
@Override
public boolean isWatchExit() {
return getProperties().WATCH_EXIT;
}
@Override
public StreamEx matchingMethods(StreamEx<Method> methods, DebugProcessImpl debugProcess) {
try {
String methodName = getMethodName();
String signature = mySignature != null ? mySignature.getName(debugProcess) : null;
return methods.filter(m -> Comparing.equal(methodName, m.name()) && Comparing.equal(signature, m.signature())).limit(1);
}
catch (EvaluateException e) {
LOG.warn(e);
}
return StreamEx.empty();
}
@Nullable
private String getMethodName() {
return getProperties().myMethodName;
}
private void setMethodName(@Nullable String methodName) {
getProperties().myMethodName = methodName;
}
private static final class MethodDescriptor {
String methodName;
JVMName methodSignature;
boolean isStatic;
int methodLine;
}
private static void processPreparedSubTypes(ReferenceType classType,
BiConsumer<ReferenceType, ClassesByNameProvider> consumer,
ProgressIndicator progressIndicator) {
long start = 0;
if (LOG.isDebugEnabled()) {
start = System.currentTimeMillis();
}
progressIndicator.setIndeterminate(false);
progressIndicator.start();
progressIndicator.setText(DebuggerBundle.message("label.method.breakpoints.processing.classes"));
try {
MultiMap<ReferenceType, ReferenceType> inheritance = new MultiMap<>();
List<ReferenceType> allTypes = classType.virtualMachine().allClasses();
for (int i = 0; i < allTypes.size(); i++) {
if (progressIndicator.isCanceled()) {
return;
}
ReferenceType type = allTypes.get(i);
if (type.isPrepared()) {
try {
DebuggerUtilsImpl.supertypes(type).forEach(st -> inheritance.putValue(st, type));
}
catch (ObjectCollectedException ignored) {
}
}
progressIndicator.setText2(i + "/" + allTypes.size());
progressIndicator.setFraction((double)i / allTypes.size());
}
List<ReferenceType> types = StreamEx.ofTree(classType, t -> StreamEx.of(inheritance.get(t))).skip(1).toList();
progressIndicator.setText(DebuggerBundle.message("label.method.breakpoints.setting.breakpoints"));
ClassesByNameProvider classesByName = ClassesByNameProvider.createCache(allTypes);
for (int i = 0; i < types.size(); i++) {
if (progressIndicator.isCanceled()) {
return;
}
consumer.accept(types.get(i), classesByName);
progressIndicator.setText2(i + "/" + types.size());
progressIndicator.setFraction((double)i / types.size());
}
if (LOG.isDebugEnabled()) {
LOG.debug("Processed " + types.size() + " classes in " + (System.currentTimeMillis() - start) + "ms");
}
}
finally {
progressIndicator.stop();
}
}
}
@@ -0,0 +1,166 @@
/*
* 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.ui.breakpoints.Breakpoint;
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointTypeBase;
import com.intellij.debugger.ui.breakpoints.MethodBreakpointPropertiesPanel;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.debugger.breakpoints.properties.JavaMethodBreakpointProperties;
import org.jetbrains.kotlin.idea.KotlinBundle;
import org.jetbrains.kotlin.psi.*;
import javax.swing.*;
import static org.jetbrains.kotlin.idea.debugger.breakpoints.BreakpointTypeUtilsKt.isBreakpointApplicable;
// This class is copied from com.intellij.debugger.ui.breakpoints.MethodBreakpoint.
// Changed parts are marked with '// MODIFICATION: ' comments.
// This should be deleted when IDEA opens the method breakpoint API (presumably in 193).
public class KotlinFunctionBreakpointType extends JavaLineBreakpointTypeBase<JavaMethodBreakpointProperties> {
// MODIFICATION: Start Kotlin implementation
public KotlinFunctionBreakpointType() {
super("kotlin-function", KotlinBundle.message("debugger.function.breakpoints.tab.title"));
}
@Override
public int getPriority() {
return 120;
}
// MODIFICATION: End Kotlin implementation
@NotNull
@Override
public Icon getEnabledIcon() {
return AllIcons.Debugger.Db_method_breakpoint;
}
@NotNull
@Override
public Icon getDisabledIcon() {
return AllIcons.Debugger.Db_disabled_method_breakpoint;
}
@NotNull
@Override
public Icon getSuspendNoneIcon() {
return AllIcons.Debugger.Db_no_suspend_method_breakpoint;
}
@NotNull
@Override
public Icon getMutedEnabledIcon() {
return AllIcons.Debugger.Db_muted_method_breakpoint;
}
@NotNull
@Override
public Icon getMutedDisabledIcon() {
return AllIcons.Debugger.Db_muted_disabled_method_breakpoint;
}
@NotNull
@Override
public Icon getInactiveDependentIcon() {
return AllIcons.Debugger.Db_dep_method_breakpoint;
}
@Override
public String getShortText(XLineBreakpoint<JavaMethodBreakpointProperties> breakpoint) {
StringBuilder buffer = new StringBuilder();
String className = breakpoint.getProperties().myClassPattern;
boolean classNameExists = className != null && className.length() > 0;
if (classNameExists) {
buffer.append(className);
}
if (breakpoint.getProperties().myMethodName != null) {
if (classNameExists) {
buffer.append(".");
}
buffer.append(breakpoint.getProperties().myMethodName);
}
return buffer.toString();
}
@Nullable
@Override
@SuppressWarnings("unchecked")
public XBreakpointCustomPropertiesPanel createCustomPropertiesPanel(@NotNull Project project) {
return new MethodBreakpointPropertiesPanel();
}
@Nullable
@Override
public JavaMethodBreakpointProperties createProperties() {
return new JavaMethodBreakpointProperties();
}
@Nullable
@Override
public JavaMethodBreakpointProperties createBreakpointProperties(@NotNull VirtualFile file, int line) {
JavaMethodBreakpointProperties properties = new JavaMethodBreakpointProperties();
if (Registry.is("debugger.emulate.method.breakpoints")) {
properties.EMULATED = true; // create all new emulated
}
return properties;
}
@NotNull
@Override
public Breakpoint<JavaMethodBreakpointProperties> createJavaBreakpoint(Project project, XBreakpoint breakpoint) {
return new KotlinFunctionBreakpoint(project, breakpoint);
}
@Override
public boolean canBeHitInOtherPlaces() {
return true;
}
// MODIFICATION: Start Kotlin implementation
@Override
public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
return isBreakpointApplicable(file, line, project, element -> {
if (element instanceof KtConstructor) {
return ApplicabilityResult.DEFINITELY_YES;
}
if (element instanceof KtClass) {
KtClass clazz = (KtClass) element;
return ApplicabilityResult.maybe(
!(clazz instanceof KtEnumEntry)
&& !clazz.isAnnotation()
&& !clazz.isInterface()
&& clazz.hasPrimaryConstructor()
);
}
if (element instanceof KtFunction) {
KtFunction function = (KtFunction) element;
return ApplicabilityResult.maybe(function.hasBody() && !KtPsiUtil.isLocal(function));
}
if (element instanceof KtPropertyAccessor) {
KtPropertyAccessor accessor = (KtPropertyAccessor) element;
KtProperty property = accessor.getProperty();
return ApplicabilityResult.maybe(accessor.hasBody() && !KtPsiUtil.isLocal(property));
}
return ApplicabilityResult.UNKNOWN;
});
}
// MODIFICATION: End Kotlin implementation
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.debugger.breakpoints
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
import com.intellij.openapi.editor.Document
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
@@ -34,9 +35,7 @@ import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.util.getLineNumber
import org.jetbrains.kotlin.idea.debugger.findElementAtLine
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.util.*
@@ -85,6 +84,75 @@ fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass
return result == breakpointTypeClass
}
class ApplicabilityResult(val isApplicable: Boolean, val shouldStop: Boolean) {
companion object {
@JvmStatic
fun definitely(result: Boolean) = ApplicabilityResult(result, shouldStop = true)
@JvmStatic
fun maybe(result: Boolean) = ApplicabilityResult(result, shouldStop = false)
@JvmField
val UNKNOWN = ApplicabilityResult(isApplicable = false, shouldStop = false)
@JvmField
val DEFINITELY_YES = ApplicabilityResult(isApplicable = true, shouldStop = true)
@JvmField
val MAYBE_YES = ApplicabilityResult(isApplicable = true, shouldStop = false)
}
}
fun isBreakpointApplicable(file: VirtualFile, line: Int, project: Project, checker: (PsiElement) -> ApplicabilityResult): Boolean {
val psiFile = PsiManager.getInstance(project).findFile(file)
if (psiFile == null || psiFile.virtualFile?.fileType != KotlinFileType.INSTANCE) {
return false
}
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
var isApplicable = false
val checked = HashSet<PsiElement>()
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun(element: PsiElement): Boolean {
if (element is PsiWhiteSpace || element.getParentOfType<PsiComment>(false) != null) {
return true
}
val parent = getTopmostParentOnLineOrSelf(element, document, line)
if (!checked.add(parent)) {
return true
}
val result = checker(parent)
if (result.shouldStop && !result.isApplicable) {
isApplicable = false
return false
}
isApplicable = isApplicable or result.isApplicable
return !result.shouldStop
})
return isApplicable
}
private fun getTopmostParentOnLineOrSelf(element: PsiElement, document: Document, line: Int): PsiElement {
var current = element
var parent = current.parent
while (parent != null) {
val offset = parent.textOffset
if (offset >= 0 && document.getLineNumber(offset) != line) break
current = parent
parent = current.parent
}
return current
}
fun computeVariants(
project: Project,
position: XSourcePosition,
+2
View File
@@ -106,9 +106,11 @@
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointType"/>
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointHandlerFactory"/>
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
+2
View File
@@ -105,9 +105,11 @@
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointType"/>
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointHandlerFactory"/>
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
+2
View File
@@ -106,9 +106,11 @@
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointType"/>
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointHandlerFactory"/>
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>