f1d89c9d904e387f066ac12259ff94517f57a2eb
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / ui / breakpoints / Breakpoint.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * Class Breakpoint
19  * @author Jeka
20  */
21 package com.intellij.debugger.ui.breakpoints;
22
23 import com.intellij.debugger.*;
24 import com.intellij.debugger.engine.*;
25 import com.intellij.debugger.engine.evaluation.*;
26 import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl;
27 import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
28 import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
29 import com.intellij.debugger.impl.DebuggerUtilsEx;
30 import com.intellij.debugger.jdi.StackFrameProxyImpl;
31 import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
32 import com.intellij.debugger.requests.ClassPrepareRequestor;
33 import com.intellij.debugger.settings.DebuggerSettings;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.util.Computable;
37 import com.intellij.openapi.util.InvalidDataException;
38 import com.intellij.openapi.util.JDOMExternalizerUtil;
39 import com.intellij.openapi.util.Key;
40 import com.intellij.psi.PsiClass;
41 import com.intellij.psi.PsiElement;
42 import com.intellij.ui.AppUIUtil;
43 import com.intellij.ui.classFilter.ClassFilter;
44 import com.intellij.util.StringBuilderSpinAllocator;
45 import com.intellij.util.ThreeState;
46 import com.intellij.xdebugger.XExpression;
47 import com.intellij.xdebugger.breakpoints.SuspendPolicy;
48 import com.intellij.xdebugger.breakpoints.XBreakpoint;
49 import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
50 import com.intellij.xdebugger.impl.XDebugSessionImpl;
51 import com.intellij.xdebugger.impl.XDebuggerHistoryManager;
52 import com.intellij.xdebugger.impl.XDebuggerUtilImpl;
53 import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
54 import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
55 import com.intellij.xdebugger.impl.breakpoints.ui.XBreakpointActionsPanel;
56 import com.sun.jdi.*;
57 import com.sun.jdi.event.LocatableEvent;
58 import org.jdom.Element;
59 import org.jetbrains.annotations.NonNls;
60 import org.jetbrains.annotations.NotNull;
61 import org.jetbrains.annotations.Nullable;
62 import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
63
64 import javax.swing.*;
65 import java.util.Arrays;
66
67 public abstract class Breakpoint<P extends JavaBreakpointProperties> implements FilteredRequestor, ClassPrepareRequestor {
68   public static final Key<Breakpoint> DATA_KEY = Key.create("JavaBreakpoint");
69
70   final XBreakpoint<P> myXBreakpoint;
71   protected final Project myProject;
72
73   @NonNls private static final String LOG_MESSAGE_OPTION_NAME = "LOG_MESSAGE";
74   public static final Breakpoint[] EMPTY_ARRAY = new Breakpoint[0];
75   protected boolean myCachedVerifiedState = false;
76
77   protected Breakpoint(@NotNull Project project, XBreakpoint<P> xBreakpoint) {
78     myProject = project;
79     myXBreakpoint = xBreakpoint;
80   }
81
82   @NotNull
83   public Project getProject() {
84     return myProject;
85   }
86
87   @NotNull
88   protected P getProperties() {
89     return myXBreakpoint.getProperties();
90   }
91
92   public XBreakpoint<P> getXBreakpoint() {
93     return myXBreakpoint;
94   }
95
96   @Nullable
97   public abstract PsiClass getPsiClass();
98   /**
99    * Request for creating all needed JPDA requests in the specified VM
100    * @param debuggerProcess the requesting process
101    */
102   public abstract void createRequest(DebugProcessImpl debugProcess);
103
104   protected boolean shouldCreateRequest(final DebugProcessImpl debugProcess) {
105     return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
106       @Override
107       public Boolean compute() {
108         JavaDebugProcess process = debugProcess.getXdebugProcess();
109         return process != null
110                && debugProcess.isAttached()
111                && ((XDebugSessionImpl)process.getSession()).isBreakpointActive(myXBreakpoint)
112                && debugProcess.getRequestsManager().findRequests(Breakpoint.this).isEmpty();
113       }
114     });
115   }
116
117   /**
118    * Request for creating all needed JPDA requests in the specified VM
119    * @param debuggerProcess the requesting process
120    */
121   @Override
122   public abstract void processClassPrepare(DebugProcess debuggerProcess, final ReferenceType referenceType);
123
124   public abstract String getDisplayName ();
125   
126   public String getShortName() {
127     return getDisplayName();
128   }
129
130   @Nullable
131   public String getClassName() {
132     return null;
133   }
134
135   public void markVerified(boolean isVerified) {
136     myCachedVerifiedState = isVerified;
137   }
138
139   public boolean isRemoveAfterHit() {
140     return myXBreakpoint instanceof XLineBreakpoint && ((XLineBreakpoint)myXBreakpoint).isTemporary();
141   }
142
143   public void setRemoveAfterHit(boolean value) {
144     if (myXBreakpoint instanceof XLineBreakpoint) {
145       ((XLineBreakpoint)myXBreakpoint).setTemporary(value);
146     }
147   }
148
149   @Nullable
150   public String getShortClassName() {
151     final String className = getClassName();
152     if (className == null) {
153       return null;
154     }
155
156     final int dotIndex = className.lastIndexOf('.');
157     return dotIndex >= 0 && dotIndex + 1 < className.length() ? className.substring(dotIndex + 1) : className;
158   }
159
160   @Nullable
161   public String getPackageName() {
162     return null;
163   }
164
165   public abstract Icon getIcon();
166
167   public abstract void reload();
168
169   /**
170    * returns UI representation
171    */
172   public abstract String getEventMessage(LocatableEvent event);
173
174   public abstract boolean isValid();
175
176   public abstract Key<? extends Breakpoint> getCategory();
177
178   /**
179    * Associates breakpoint with class.
180    *    Create requests for loaded class and registers callback for loading classes
181    * @param debugProcess the requesting process
182    */
183   protected void createOrWaitPrepare(DebugProcessImpl debugProcess, String classToBeLoaded) {
184     debugProcess.getRequestsManager().callbackOnPrepareClasses(this, classToBeLoaded);
185
186     debugProcess.getVirtualMachineProxy().classesByName(classToBeLoaded).stream()
187       .filter(ReferenceType::isPrepared)
188       .forEach(aList -> processClassPrepare(debugProcess, aList));
189   }
190
191   protected void createOrWaitPrepare(final DebugProcessImpl debugProcess, @NotNull final SourcePosition classPosition) {
192     debugProcess.getRequestsManager().callbackOnPrepareClasses(this, classPosition);
193
194     debugProcess.getPositionManager().getAllClasses(classPosition).stream()
195       .filter(ReferenceType::isPrepared)
196       .forEach(refType -> processClassPrepare(debugProcess, refType));
197   }
198
199   protected ObjectReference getThisObject(SuspendContextImpl context, LocatableEvent event) throws EvaluateException {
200     ThreadReferenceProxyImpl thread = context.getThread();
201     if(thread != null) {
202       StackFrameProxyImpl stackFrameProxy = thread.frame(0);
203       if(stackFrameProxy != null) {
204         return stackFrameProxy.thisObject();
205       }
206     }
207     return null;
208   }
209
210   @Override
211   public boolean processLocatableEvent(final SuspendContextCommandImpl action, final LocatableEvent event) throws EventProcessingException {
212     final SuspendContextImpl context = action.getSuspendContext();
213     if(!isValid()) {
214       context.getDebugProcess().getRequestsManager().deleteRequest(this);
215       return false;
216     }
217
218     final String[] title = {DebuggerBundle.message("title.error.evaluating.breakpoint.condition") };
219
220     try {
221       final StackFrameProxyImpl frameProxy = context.getThread().frame(0);
222       if (frameProxy == null) {
223         // might be if the thread has been collected
224         return false;
225       }
226
227       final EvaluationContextImpl evaluationContext = new EvaluationContextImpl(
228         action.getSuspendContext(),
229         frameProxy,
230         getThisObject(context, event)
231       );
232
233       if(!evaluateCondition(evaluationContext, event)) {
234         return false;
235       }
236
237       title[0] = DebuggerBundle.message("title.error.evaluating.breakpoint.action");
238       runAction(evaluationContext, event);
239     }
240     catch (final EvaluateException ex) {
241       if(ApplicationManager.getApplication().isUnitTestMode()) {
242         System.out.println(ex.getMessage());
243         return false;
244       }
245
246       throw new EventProcessingException(title[0], ex.getMessage(), ex);
247     } 
248
249     return true;
250   }
251
252   private void runAction(final EvaluationContextImpl context, LocatableEvent event) {
253     final DebugProcessImpl debugProcess = context.getDebugProcess();
254     if (isLogEnabled() || isLogExpressionEnabled()) {
255       final StringBuilder buf = StringBuilderSpinAllocator.alloc();
256       try {
257         if (myXBreakpoint.isLogMessage()) {
258           buf.append(getEventMessage(event));
259           buf.append("\n");
260         }
261         if (isLogExpressionEnabled()) {
262           if(!debugProcess.isAttached()) {
263             return;
264           }
265
266           final TextWithImports expressionToEvaluate = getLogMessage();
267           try {
268             SourcePosition position = ContextUtil.getSourcePosition(context);
269             PsiElement element = ContextUtil.getContextElement(context, position);
270             ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(myProject, new EvaluatingComputable<ExpressionEvaluator>() {
271               @Override
272               public ExpressionEvaluator compute() throws EvaluateException {
273                 return EvaluatorBuilderImpl.build(expressionToEvaluate, element, position, myProject);
274               }
275             });
276             final Value eval = evaluator.evaluate(context);
277             final String result = eval instanceof VoidValue ? "void" : DebuggerUtils.getValueAsString(context, eval);
278             buf.append(result);
279           }
280           catch (EvaluateException e) {
281             buf.append(DebuggerBundle.message("error.unable.to.evaluate.expression"));
282             buf.append(" \"");
283             buf.append(expressionToEvaluate);
284             buf.append("\"");
285             buf.append(" : ");
286             buf.append(e.getMessage());
287           }
288           buf.append("\n");
289         }
290         if (buf.length() > 0) {
291           debugProcess.printToConsole(buf.toString());
292         }
293       }
294       finally {
295         StringBuilderSpinAllocator.dispose(buf);
296       }
297     }
298     if (isRemoveAfterHit()) {
299       handleTemporaryBreakpointHit(debugProcess);
300     }
301   }
302
303   /**
304    * @return true if the ID was added or false otherwise
305    */
306   private boolean hasObjectID(long id) {
307     return Arrays.stream(getInstanceFilters()).anyMatch(instanceFilter -> instanceFilter.getId() == id);
308   }
309
310   public boolean evaluateCondition(final EvaluationContextImpl context, LocatableEvent event) throws EvaluateException {
311     final DebugProcessImpl debugProcess = context.getDebugProcess();
312     if (isCountFilterEnabled()) {
313       debugProcess.getVirtualMachineProxy().suspend();
314       debugProcess.getRequestsManager().deleteRequest(this);
315       createRequest(debugProcess);
316       debugProcess.getVirtualMachineProxy().resume();
317     }
318     if (isInstanceFiltersEnabled()) {
319       Value value = context.getThisObject();
320       if (value != null) {  // non-static
321         ObjectReference reference = (ObjectReference)value;
322         if (!hasObjectID(reference.uniqueID())) {
323           return false;
324         }
325       }
326     }
327
328     if (isClassFiltersEnabled() &&
329         !typeMatchesClassFilters(calculateEventClass(context, event), getClassFilters(), getClassExclusionFilters())) {
330       return false;
331     }
332
333     if (!isConditionEnabled() || getCondition().getText().isEmpty()) {
334       return true;
335     }
336
337     StackFrameProxyImpl frame = context.getFrameProxy();
338     if (frame != null) {
339       Location location = frame.location();
340       if (location != null) {
341         ThreeState result = debugProcess.getPositionManager().evaluateCondition(context, frame, location, getCondition().getText());
342         if (result != ThreeState.UNSURE) {
343           return result == ThreeState.YES;
344         }
345       }
346     }
347
348     try {
349       Project project = context.getProject();
350       SourcePosition contextSourcePosition = ContextUtil.getSourcePosition(context);
351       ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(project, new EvaluatingComputable<ExpressionEvaluator>() {
352         @Override
353         public ExpressionEvaluator compute() throws EvaluateException {
354           // IMPORTANT: calculate context psi element basing on the location where the exception
355           // has been hit, not on the location where it was set. (For line breakpoints these locations are the same, however,
356           // for method, exception and field breakpoints these locations differ)
357           PsiElement contextPsiElement = ContextUtil.getContextElement(contextSourcePosition);
358           if (contextPsiElement == null) {
359             contextPsiElement = getEvaluationElement(); // as a last resort
360           }
361           return EvaluatorBuilderImpl.build(getCondition(), contextPsiElement, contextSourcePosition, project);
362         }
363       });
364       return DebuggerUtilsEx.evaluateBoolean(evaluator, context);
365     }
366     catch (EvaluateException ex) {
367       if (ex.getCause() instanceof VMDisconnectedException) {
368         return false;
369       }
370       throw EvaluateExceptionUtil.createEvaluateException(
371         DebuggerBundle.message("error.failed.evaluating.breakpoint.condition", getCondition(), ex.getMessage())
372       );
373     }
374   }
375
376   protected String calculateEventClass(EvaluationContextImpl context, LocatableEvent event) throws EvaluateException {
377     return event.location().declaringType().name();
378   }
379
380   protected static boolean typeMatchesClassFilters(@Nullable String typeName, ClassFilter[] includeFilters, ClassFilter[] exludeFilters) {
381     if (typeName == null) {
382       return true;
383     }
384     boolean matches = false, hasEnabled = false;
385     for (ClassFilter classFilter : includeFilters) {
386       if (classFilter.isEnabled()) {
387         hasEnabled = true;
388         if (classFilter.matches(typeName)) {
389           matches = true;
390           break;
391         }
392       }
393     }
394     if (hasEnabled && !matches) {
395       return false;
396     }
397     return Arrays.stream(exludeFilters).noneMatch(classFilter -> classFilter.isEnabled() && classFilter.matches(typeName));
398   }
399
400   private void handleTemporaryBreakpointHit(final DebugProcessImpl debugProcess) {
401     // need to delete the request immediately, see IDEA-133978
402     debugProcess.getRequestsManager().deleteRequest(this);
403
404     debugProcess.addDebugProcessListener(new DebugProcessListener() {
405       @Override
406       public void resumed(SuspendContext suspendContext) {
407         removeBreakpoint();
408       }
409
410       @Override
411       public void processDetached(DebugProcess process, boolean closedByUser) {
412         removeBreakpoint();
413       }
414
415       private void removeBreakpoint() {
416         AppUIUtil.invokeOnEdt(() -> DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().removeBreakpoint(Breakpoint.this));
417         debugProcess.removeDebugProcessListener(this);
418       }
419     });
420   }
421
422   public void updateUI() {
423   }
424
425   public void readExternal(Element parentNode) throws InvalidDataException {
426     FilteredRequestorImpl requestor = new FilteredRequestorImpl(myProject);
427     requestor.readTo(parentNode, this);
428     try {
429       setEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "ENABLED")));
430     }
431     catch (Exception ignored) {
432     }
433     try {
434       setLogEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "LOG_ENABLED")));
435     }
436     catch (Exception ignored) {
437     }
438     try {
439       String logMessage = JDOMExternalizerUtil.readField(parentNode, LOG_MESSAGE_OPTION_NAME);
440       if (logMessage != null && !logMessage.isEmpty()) {
441         XExpressionImpl expression = XExpressionImpl.fromText(logMessage);
442         XDebuggerHistoryManager.getInstance(myProject).addRecentExpression(XBreakpointActionsPanel.LOG_EXPRESSION_HISTORY_ID, expression);
443         myXBreakpoint.setLogExpressionObject(expression);
444         ((XBreakpointBase)myXBreakpoint).setLogExpressionEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "LOG_EXPRESSION_ENABLED")));
445       }
446     }
447     catch (Exception ignored) {
448     }
449     try {
450       setRemoveAfterHit(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "REMOVE_AFTER_HIT")));
451     }
452     catch (Exception ignored) {
453     }
454   }
455
456   @Nullable
457   public abstract PsiElement getEvaluationElement();
458
459   protected TextWithImports getLogMessage() {
460     return TextWithImportsImpl.fromXExpression(myXBreakpoint.getLogExpressionObject());
461   }
462
463   protected TextWithImports getCondition() {
464     return TextWithImportsImpl.fromXExpression(myXBreakpoint.getConditionExpression());
465   }
466
467   public boolean isEnabled() {
468     return myXBreakpoint.isEnabled();
469   }
470
471   public void setEnabled(boolean enabled) {
472     myXBreakpoint.setEnabled(enabled);
473   }
474
475   protected boolean isLogEnabled() {
476     return myXBreakpoint.isLogMessage();
477   }
478
479   public void setLogEnabled(boolean logEnabled) {
480     myXBreakpoint.setLogMessage(logEnabled);
481   }
482
483   protected boolean isLogExpressionEnabled() {
484     XExpression expression = myXBreakpoint.getLogExpressionObject();
485     if (XDebuggerUtilImpl.isEmptyExpression(expression)) {
486       return false;
487     }
488     return !getLogMessage().isEmpty();
489   }
490
491   @Override
492   public boolean isCountFilterEnabled() {
493     return getProperties().isCOUNT_FILTER_ENABLED();
494   }
495   public void setCountFilterEnabled(boolean enabled) {
496     if (getProperties().setCOUNT_FILTER_ENABLED(enabled)) {
497       fireBreakpointChanged();
498     }
499   }
500
501   @Override
502   public int getCountFilter() {
503     return getProperties().getCOUNT_FILTER();
504   }
505
506   public void setCountFilter(int filter) {
507     if (getProperties().setCOUNT_FILTER(filter)) {
508       fireBreakpointChanged();
509     }
510   }
511
512   @Override
513   public boolean isClassFiltersEnabled() {
514     return getProperties().isCLASS_FILTERS_ENABLED();
515   }
516
517   public void setClassFiltersEnabled(boolean enabled) {
518     if (getProperties().setCLASS_FILTERS_ENABLED(enabled)) {
519       fireBreakpointChanged();
520     }
521   }
522
523   @Override
524   public ClassFilter[] getClassFilters() {
525     return getProperties().getClassFilters();
526   }
527
528   public void setClassFilters(ClassFilter[] filters) {
529     if (getProperties().setClassFilters(filters)) {
530       fireBreakpointChanged();
531     }
532   }
533
534   @Override
535   public ClassFilter[] getClassExclusionFilters() {
536     return getProperties().getClassExclusionFilters();
537   }
538
539   public void setClassExclusionFilters(ClassFilter[] filters) {
540     if (getProperties().setClassExclusionFilters(filters)) {
541       fireBreakpointChanged();
542     }
543   }
544
545   @Override
546   public boolean isInstanceFiltersEnabled() {
547     return getProperties().isINSTANCE_FILTERS_ENABLED();
548   }
549
550   public void setInstanceFiltersEnabled(boolean enabled) {
551     if (getProperties().setINSTANCE_FILTERS_ENABLED(enabled)) {
552       fireBreakpointChanged();
553     }
554   }
555
556   @Override
557   public InstanceFilter[] getInstanceFilters() {
558     return getProperties().getInstanceFilters();
559   }
560
561   public void setInstanceFilters(InstanceFilter[] filters) {
562     if (getProperties().setInstanceFilters(filters)) {
563       fireBreakpointChanged();
564     }
565   }
566
567   private static String getSuspendPolicy(XBreakpoint breakpoint) {
568     switch (breakpoint.getSuspendPolicy()) {
569       case ALL:
570         return DebuggerSettings.SUSPEND_ALL;
571       case THREAD:
572         return DebuggerSettings.SUSPEND_THREAD;
573       case NONE:
574         return DebuggerSettings.SUSPEND_NONE;
575
576       default:
577         throw new IllegalArgumentException("unknown suspend policy");
578     }
579   }
580
581   static SuspendPolicy transformSuspendPolicy(String policy) {
582     if (DebuggerSettings.SUSPEND_ALL.equals(policy)) {
583       return SuspendPolicy.ALL;
584     } else if (DebuggerSettings.SUSPEND_THREAD.equals(policy)) {
585       return SuspendPolicy.THREAD;
586     } else if (DebuggerSettings.SUSPEND_NONE.equals(policy)) {
587       return SuspendPolicy.NONE;
588     } else {
589       throw new IllegalArgumentException("unknown suspend policy");
590     }
591   }
592
593   protected boolean isSuspend() {
594     return myXBreakpoint.getSuspendPolicy() != SuspendPolicy.NONE;
595   }
596
597   @Override
598   public String getSuspendPolicy() {
599     return getSuspendPolicy(myXBreakpoint);
600   }
601
602   public void setSuspendPolicy(String policy) {
603     myXBreakpoint.setSuspendPolicy(transformSuspendPolicy(policy));
604   }
605
606   protected boolean isConditionEnabled() {
607     XExpression condition = myXBreakpoint.getConditionExpression();
608     if (XDebuggerUtilImpl.isEmptyExpression(condition)) {
609       return false;
610     }
611     return !getCondition().isEmpty();
612   }
613
614   public void setCondition(@Nullable TextWithImports condition) {
615     myXBreakpoint.setConditionExpression(TextWithImportsImpl.toXExpression(condition));
616   }
617
618   public void addInstanceFilter(long l) {
619     getProperties().addInstanceFilter(l);
620   }
621
622   protected void fireBreakpointChanged() {
623     ((XBreakpointBase)myXBreakpoint).fireBreakpointChanged();
624   }
625 }