mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
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:
@@ -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,
|
||||
|
||||
+63
-57
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user