emulated method breakpoints now work in lambdas

This commit is contained in:
Egor.Ushakov
2016-10-28 19:59:21 +03:00
parent 401e862cc8
commit 3f55058b6d
9 changed files with 93 additions and 82 deletions
@@ -34,11 +34,6 @@ import com.intellij.psi.PsiClass;
import com.intellij.util.containers.ContainerUtil;
import com.sun.jdi.*;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class JumpToObjectAction extends DebuggerAction{
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.actions.JumpToObjectAction");
@@ -98,35 +93,19 @@ public class JumpToObjectAction extends DebuggerAction{
}
try {
if(type instanceof ArrayType) {
if (type instanceof ArrayType) {
type = ((ArrayType)type).componentType();
}
if(type instanceof ClassType) {
final ClassType clsType = (ClassType)type;
Location lambdaLocation = null;
if (DebuggerUtilsEx.isLambdaClassName(clsType.name())) {
List<Method> applicableMethods = ContainerUtil.filter(clsType.methods(), m -> m.isPublic() && !m.isBridge());
if (applicableMethods.size() == 1) {
AtomicReference<Location> locationRef = new AtomicReference<>();
MethodBytecodeUtil.visit(clsType, applicableMethods.get(0), new MethodVisitor(Opcodes.API_VERSION) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
ReferenceType cls = ContainerUtil.getFirstItem(clsType.virtualMachine().classesByName(owner));
if (cls != null) {
Method method = ContainerUtil.getFirstItem(cls.methodsByName(name));
if (method != null) {
Location loc = ContainerUtil.getFirstItem(DebuggerUtilsEx.allLineLocations(method));
if (loc != null) {
locationRef.set(loc);
}
}
}
}
});
lambdaLocation = locationRef.get();
}
if (type instanceof ClassType) {
ClassType clsType = (ClassType)type;
Method lambdaMethod = MethodBytecodeUtil.getLambdaMethod(clsType);
Location location = lambdaMethod != null ? ContainerUtil.getFirstItem(DebuggerUtilsEx.allLineLocations(lambdaMethod)) : null;
if (location == null) {
location = ContainerUtil.getFirstItem(clsType.allLineLocations());
}
final Location location = lambdaLocation != null ? lambdaLocation : ContainerUtil.getFirstItem(clsType.allLineLocations());
if (location != null) {
SourcePosition position = debugProcess.getPositionManager().getSourcePosition(location);
return ApplicationManager.getApplication().runReadAction(new Computable<SourcePosition>() {
@@ -69,7 +69,7 @@ public class BasicStepMethodFilter implements NamedMethodFilter {
Method method = location.method();
String name = method.name();
if (!myTargetMethodName.equals(name)) {
if (LambdaMethodFilter.isLambdaName(name)) {
if (DebuggerUtilsEx.isLambdaName(name)) {
SourcePosition position = process.getPositionManager().getSourcePosition(location);
return ApplicationManager.getApplication().runReadAction((Computable<Boolean>)() -> {
PsiElement psiMethod = DebuggerUtilsEx.getContainingMethod(position);
@@ -26,7 +26,7 @@ public class DefaultSyntheticProvider implements SyntheticTypeComponentProvider
@Override
public boolean isSynthetic(TypeComponent typeComponent) {
String name = typeComponent.name();
if (LambdaMethodFilter.isLambdaName(name)) {
if (DebuggerUtilsEx.isLambdaName(name)) {
return false;
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-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.
@@ -17,8 +17,8 @@ package com.intellij.debugger.engine;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiLambdaExpression;
@@ -34,7 +34,6 @@ import org.jetbrains.annotations.Nullable;
* Date: 10/26/13
*/
public class LambdaMethodFilter implements BreakpointStepMethodFilter {
public static final String LAMBDA_METHOD_PREFIX = "lambda$";
private final int myLambdaOrdinal;
@Nullable
private final SourcePosition myFirstStatementPosition;
@@ -82,7 +81,7 @@ public class LambdaMethodFilter implements BreakpointStepMethodFilter {
public boolean locationMatches(DebugProcessImpl process, Location location) throws EvaluateException {
final VirtualMachineProxyImpl vm = process.getVirtualMachineProxy();
final Method method = location.method();
return isLambdaName(method.name()) && (!vm.canGetSyntheticAttribute() || method.isSynthetic());
return DebuggerUtilsEx.isLambdaName(method.name()) && (!vm.canGetSyntheticAttribute() || method.isSynthetic());
}
@Nullable
@@ -91,10 +90,6 @@ public class LambdaMethodFilter implements BreakpointStepMethodFilter {
return myCallingExpressionLines;
}
public static boolean isLambdaName(@Nullable String name) {
return !StringUtil.isEmpty(name) && name.startsWith(LAMBDA_METHOD_PREFIX);
}
public static int getLambdaOrdinal(@NotNull String name) {
int pos = name.lastIndexOf('$');
if (pos > -1) {
@@ -192,11 +192,11 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
}
int lambdaOrdinal = -1;
if (LambdaMethodFilter.isLambdaName(method.name())) {
if (DebuggerUtilsEx.isLambdaName(method.name())) {
Set<Method> lambdas =
ContainerUtil.map2SetNotNull(locationsOfLine(location.declaringType(), sourcePosition), location1 -> {
Method method1 = location1.method();
if (LambdaMethodFilter.isLambdaName(method1.name())) {
if (DebuggerUtilsEx.isLambdaName(method1.name())) {
return method1;
}
return null;
@@ -233,7 +233,7 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
return null;
}
else if (((method instanceof PsiMethod && myExpectedMethodName.equals(((PsiMethod)method).getName())) ||
(method instanceof PsiLambdaExpression && LambdaMethodFilter.isLambdaName(myExpectedMethodName))) &&
(method instanceof PsiLambdaExpression && DebuggerUtilsEx.isLambdaName(myExpectedMethodName))) &&
insideBody(element, DebuggerUtilsEx.getBody(method))) {
return element;
}
@@ -256,7 +256,7 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
public SourcePosition compute() {
PsiFile file = original.getFile();
int line = original.getLine();
if (LambdaMethodFilter.isLambdaName(myExpectedMethodName) && myLambdaOrdinal > -1) {
if (DebuggerUtilsEx.isLambdaName(myExpectedMethodName) && myLambdaOrdinal > -1) {
List<PsiLambdaExpression> lambdas = DebuggerUtilsEx.collectLambdas(original, true);
Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
@@ -803,6 +803,10 @@ public abstract class DebuggerUtilsEx extends DebuggerUtils {
return StringUtil.substringBefore(typeName, "$$Lambda$");
}
public static boolean isLambdaName(@Nullable String name) {
return !StringUtil.isEmpty(name) && name.startsWith("lambda$");
}
public static List<PsiLambdaExpression> collectLambdas(@NotNull SourcePosition position, final boolean onlyOnTheLine) {
ApplicationManager.getApplication().assertReadAccessAllowed();
PsiFile file = position.getFile();
@@ -20,7 +20,6 @@ import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.NoDataException;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.LambdaMethodFilter;
import com.intellij.debugger.engine.PositionManagerImpl;
import com.intellij.debugger.engine.SuspendContextImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
@@ -103,7 +102,7 @@ public class SourceCodeChecker {
method.isBridge() ||
method.isStaticInitializer() ||
(method.declaringType() instanceof ClassType && ((ClassType)method.declaringType()).isEnum()) ||
LambdaMethodFilter.isLambdaName(method.name())) {
DebuggerUtilsEx.isLambdaName(method.name())) {
return ThreeState.UNSURE;
}
List<Location> locations = DebuggerUtilsEx.allLineLocations(method);
@@ -16,8 +16,10 @@
package com.intellij.debugger.jdi;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.openapi.util.Ref;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.ThrowableConsumer;
import com.intellij.util.containers.ContainerUtil;
import com.sun.jdi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -220,4 +222,27 @@ public class MethodBytecodeUtil {
return OBJECT_TYPE;
}
}
@Nullable
public static Method getLambdaMethod(ReferenceType clsType) {
Ref<Method> methodRef = Ref.create();
if (DebuggerUtilsEx.isLambdaClassName(clsType.name())) {
List<Method> applicableMethods = ContainerUtil.filter(clsType.methods(), m -> m.isPublic() && !m.isBridge());
if (applicableMethods.size() == 1) {
visit(clsType, applicableMethods.get(0), new MethodVisitor(Opcodes.API_VERSION) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
ReferenceType cls = ContainerUtil.getFirstItem(clsType.virtualMachine().classesByName(owner));
if (cls != null) {
Method method = ContainerUtil.getFirstItem(cls.methodsByName(name));
if (method != null) {
methodRef.set(method);
}
}
}
});
}
}
return methodRef.get();
}
}
@@ -153,44 +153,50 @@ public class MethodBreakpoint extends BreakpointWithHighlighter<JavaMethodBreakp
return;
}
try {
for (Method method : classType.methods()) {
if (getMethodName().equals(method.name()) && mySignature.getName(debugProcess).equals(method.signature())) {
List<Location> allLineLocations = DebuggerUtilsEx.allLineLocations(method);
if (!allLineLocations.isEmpty()) {
if (isWatchEntry()) {
createLocationBreakpointRequest(ContainerUtil.getFirstItem(allLineLocations), debugProcess);
}
if (isWatchExit()) {
MethodBytecodeUtil.visit(classType, method, new MethodVisitor(Opcodes.API_VERSION) {
int myLastLine = 0;
@Override
public void visitLineNumber(int line, Label start) {
myLastLine = line;
}
Method method = MethodBytecodeUtil.getLambdaMethod(classType);
if (method == null) {
for (Method m : classType.methods()) {
if (getMethodName().equals(m.name()) && mySignature.getName(debugProcess).equals(m.signature())) {
method = m;
break;
}
}
}
if (method != null) {
List<Location> allLineLocations = DebuggerUtilsEx.allLineLocations(method);
if (!allLineLocations.isEmpty()) {
if (isWatchEntry()) {
createLocationBreakpointRequest(ContainerUtil.getFirstItem(allLineLocations), debugProcess);
}
if (isWatchExit()) {
MethodBytecodeUtil.visit(classType, 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(location, debugProcess));
}
@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(location, debugProcess));
}
});
}
}
});
}
if (base) {
// desired class found - now also track all new classes
createRequestForSubClasses(debugProcess, classType);
}
break;
}
if (base) {
// desired class found - now also track all new classes
createRequestForSubClasses(debugProcess, classType);
}
}
}
@@ -358,6 +364,9 @@ public class MethodBreakpoint extends BreakpointWithHighlighter<JavaMethodBreakp
}
public boolean matchesEvent(@NotNull final LocatableEvent event, final DebugProcessImpl process) throws EvaluateException {
if (isEmulated()) {
return true;
}
if (getMethodName() == null || mySignature == null) {
return false;
}