IDEA-162947 Add ability to use lambda expressions in breakpoint conditions

cache condition and log message evaluators to avoid recompilation on every hit
This commit is contained in:
Egor.Ushakov
2016-10-26 13:15:36 +03:00
parent c9969720dd
commit 8179d013a1
2 changed files with 112 additions and 69 deletions
@@ -58,6 +58,7 @@ import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
import com.intellij.xdebugger.impl.breakpoints.ui.XBreakpointActionsPanel;
import com.sun.jdi.*;
import com.sun.jdi.event.LocatableEvent;
import com.sun.jdi.request.EventRequest;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -258,17 +259,19 @@ public abstract class Breakpoint<P extends JavaBreakpointProperties> implements
return;
}
TextWithImports logMessage = getLogMessage();
try {
SourcePosition position = ContextUtil.getSourcePosition(context);
PsiElement element = ContextUtil.getContextElement(context, position);
ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(myProject, () ->
createExpressionEvaluator(myProject, element, position, getLogMessage(), this::createLogMessageCodeFragment));
ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(myProject,
() -> EvaluatorCache.cacheOrGet("LogMessageEvaluator", event.request(), element, logMessage, () ->
createExpressionEvaluator(myProject, element, position, logMessage, this::createLogMessageCodeFragment)));
Value eval = evaluator.evaluate(context);
buf.append(eval instanceof VoidValue ? "void" : DebuggerUtils.getValueAsString(context, eval));
}
catch (EvaluateException e) {
buf.append(DebuggerBundle.message("error.unable.to.evaluate.expression"))
.append(" \"").append(getLogMessage()).append("\"")
.append(" \"").append(logMessage).append("\"")
.append(" : ").append(e.getMessage());
}
buf.append("\n");
@@ -316,7 +319,12 @@ public abstract class Breakpoint<P extends JavaBreakpointProperties> implements
return false;
}
if (!isConditionEnabled() || getCondition().getText().isEmpty()) {
if (!isConditionEnabled()) {
return true;
}
TextWithImports condition = getCondition();
if (condition.isEmpty()) {
return true;
}
@@ -324,7 +332,7 @@ public abstract class Breakpoint<P extends JavaBreakpointProperties> implements
if (frame != null) {
Location location = frame.location();
if (location != null) {
ThreeState result = debugProcess.getPositionManager().evaluateCondition(context, frame, location, getCondition().getText());
ThreeState result = debugProcess.getPositionManager().evaluateCondition(context, frame, location, condition.getText());
if (result != ThreeState.UNSURE) {
return result == ThreeState.YES;
}
@@ -337,12 +345,14 @@ public abstract class Breakpoint<P extends JavaBreakpointProperties> implements
// IMPORTANT: calculate context psi element basing on the location where the exception
// has been hit, not on the location where it was set. (For line breakpoints these locations are the same, however,
// for method, exception and field breakpoints these locations differ)
PsiElement contextPsiElement = ContextUtil.getContextElement(contextSourcePosition);
if (contextPsiElement == null) {
contextPsiElement = getEvaluationElement(); // as a last resort
}
return createExpressionEvaluator(myProject, contextPsiElement, contextSourcePosition, getCondition(),
this::createConditionCodeFragment);
PsiElement contextElement = ContextUtil.getContextElement(contextSourcePosition);
PsiElement contextPsiElement = contextElement != null ? contextElement : getEvaluationElement(); // as a last resort
return EvaluatorCache.cacheOrGet("ConditionEvaluator", event.request(), contextPsiElement, condition,
() -> createExpressionEvaluator(myProject,
contextPsiElement,
contextSourcePosition,
condition,
this::createConditionCodeFragment));
});
return DebuggerUtilsEx.evaluateBoolean(evaluator, context);
}
@@ -351,11 +361,38 @@ public abstract class Breakpoint<P extends JavaBreakpointProperties> implements
return false;
}
throw EvaluateExceptionUtil.createEvaluateException(
DebuggerBundle.message("error.failed.evaluating.breakpoint.condition", getCondition(), ex.getMessage())
DebuggerBundle.message("error.failed.evaluating.breakpoint.condition", condition, ex.getMessage())
);
}
}
private static class EvaluatorCache {
private final PsiElement myContext;
private final TextWithImports myTextWithImports;
private final ExpressionEvaluator myEvaluator;
private EvaluatorCache(PsiElement context, TextWithImports textWithImports, ExpressionEvaluator evaluator) {
myContext = context;
myTextWithImports = textWithImports;
myEvaluator = evaluator;
}
@Nullable
static ExpressionEvaluator cacheOrGet(String propertyName,
EventRequest request,
PsiElement context,
TextWithImports text,
EvaluatingComputable<ExpressionEvaluator> supplier) throws EvaluateException {
EvaluatorCache cache = (EvaluatorCache)request.getProperty(propertyName);
if (cache != null && cache.myContext.equals(context) && cache.myTextWithImports.equals(text)) {
return cache.myEvaluator;
}
ExpressionEvaluator evaluator = supplier.compute();
request.putProperty(propertyName, new EvaluatorCache(context, text, evaluator));
return evaluator;
}
}
private static ExpressionEvaluator createExpressionEvaluator(Project project,
PsiElement contextPsiElement,
SourcePosition contextSourcePosition,
@@ -53,6 +53,8 @@ import java.util.function.Function;
// todo: consider batching compilations in order not to start a separate process for every class that needs to be compiled
public class CompilingEvaluatorImpl extends CompilingEvaluator {
private Collection<ClassObject> myCompiledClasses;
public CompilingEvaluatorImpl(@NotNull Project project,
@NotNull PsiElement context,
@NotNull ExtractLightMethodObjectHandler.ExtractedData data) {
@@ -62,67 +64,71 @@ public class CompilingEvaluatorImpl extends CompilingEvaluator {
@Override
@NotNull
protected Collection<ClassObject> compile(@Nullable JavaSdkVersion debuggeeVersion) throws EvaluateException {
Module module = ApplicationManager.getApplication().runReadAction(
(Computable<Module>)() -> ModuleUtilCore.findModuleForPsiElement(myPsiContext));
List<String> options = new ArrayList<>();
options.add("-encoding");
options.add("UTF-8");
List<File> platformClasspath = new ArrayList<>();
List<File> classpath = new ArrayList<>();
AnnotationProcessingConfiguration profile = null;
if (module != null) {
assert myProject.equals(module.getProject()) : module + " is from another project";
profile = CompilerConfiguration.getInstance(myProject).getAnnotationProcessingConfiguration(module);
ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
for (String s : rootManager.orderEntries().compileOnly().recursively().exportedOnly().withoutSdk().getPathsList().getPathList()) {
classpath.add(new File(s));
}
for (String s : rootManager.orderEntries().compileOnly().sdkOnly().getPathsList().getPathList()) {
platformClasspath.add(new File(s));
}
}
JavaBuilder.addAnnotationProcessingOptions(options, profile);
Pair<Sdk, JavaSdkVersion> runtime = BuildManager.getJavacRuntimeSdk(myProject);
JavaSdkVersion buildRuntimeVersion = runtime.getSecond();
// if compiler or debuggee version or both are unknown, let source and target be the compiler's defaults
if (buildRuntimeVersion != null && debuggeeVersion != null) {
JavaSdkVersion minVersion = buildRuntimeVersion.ordinal() > debuggeeVersion.ordinal() ? debuggeeVersion : buildRuntimeVersion;
String sourceOption = getSourceOption(minVersion.getMaxLanguageLevel());
options.add("-source");
options.add(sourceOption);
options.add("-target");
options.add(sourceOption);
}
CompilerManager compilerManager = CompilerManager.getInstance(myProject);
File sourceFile = null;
try {
sourceFile = generateTempSourceFile(compilerManager.getJavacCompilerWorkingDir());
File srcDir = sourceFile.getParentFile();
List<File> sourcePath = Collections.emptyList();
Set<File> sources = Collections.singleton(sourceFile);
return compilerManager.compileJavaCode(options, platformClasspath, classpath, Collections.emptyList(), sourcePath, sources, srcDir);
}
catch (CompilationException e) {
StringBuilder res = new StringBuilder("Compilation failed:\n");
for (CompilationException.Message m : e.getMessages()) {
if (m.getCategory() == CompilerMessageCategory.ERROR) {
res.append(m.getText()).append("\n");
if (myCompiledClasses == null) {
Module module = ApplicationManager.getApplication().runReadAction(
(Computable<Module>)() -> ModuleUtilCore.findModuleForPsiElement(myPsiContext));
List<String> options = new ArrayList<>();
options.add("-encoding");
options.add("UTF-8");
List<File> platformClasspath = new ArrayList<>();
List<File> classpath = new ArrayList<>();
AnnotationProcessingConfiguration profile = null;
if (module != null) {
assert myProject.equals(module.getProject()) : module + " is from another project";
profile = CompilerConfiguration.getInstance(myProject).getAnnotationProcessingConfiguration(module);
ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
for (String s : rootManager.orderEntries().compileOnly().recursively().exportedOnly().withoutSdk().getPathsList().getPathList()) {
classpath.add(new File(s));
}
for (String s : rootManager.orderEntries().compileOnly().sdkOnly().getPathsList().getPathList()) {
platformClasspath.add(new File(s));
}
}
throw new EvaluateException(res.toString());
}
catch (Exception e) {
throw new EvaluateException(e.getMessage());
}
finally {
if (sourceFile != null) {
FileUtil.delete(sourceFile);
JavaBuilder.addAnnotationProcessingOptions(options, profile);
Pair<Sdk, JavaSdkVersion> runtime = BuildManager.getJavacRuntimeSdk(myProject);
JavaSdkVersion buildRuntimeVersion = runtime.getSecond();
// if compiler or debuggee version or both are unknown, let source and target be the compiler's defaults
if (buildRuntimeVersion != null && debuggeeVersion != null) {
JavaSdkVersion minVersion = buildRuntimeVersion.ordinal() > debuggeeVersion.ordinal() ? debuggeeVersion : buildRuntimeVersion;
String sourceOption = getSourceOption(minVersion.getMaxLanguageLevel());
options.add("-source");
options.add(sourceOption);
options.add("-target");
options.add(sourceOption);
}
CompilerManager compilerManager = CompilerManager.getInstance(myProject);
File sourceFile = null;
try {
sourceFile = generateTempSourceFile(compilerManager.getJavacCompilerWorkingDir());
File srcDir = sourceFile.getParentFile();
List<File> sourcePath = Collections.emptyList();
Set<File> sources = Collections.singleton(sourceFile);
myCompiledClasses =
compilerManager.compileJavaCode(options, platformClasspath, classpath, Collections.emptyList(), sourcePath, sources, srcDir);
}
catch (CompilationException e) {
StringBuilder res = new StringBuilder("Compilation failed:\n");
for (CompilationException.Message m : e.getMessages()) {
if (m.getCategory() == CompilerMessageCategory.ERROR) {
res.append(m.getText()).append("\n");
}
}
throw new EvaluateException(res.toString());
}
catch (Exception e) {
throw new EvaluateException(e.getMessage());
}
finally {
if (sourceFile != null) {
FileUtil.delete(sourceFile);
}
}
}
return myCompiledClasses;
}
@NotNull