CPP-548 Integration with Valgrind memory analyser
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ex / GlobalInspectionContextImpl.java
1 /*
2  * Copyright 2000-2017 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 package com.intellij.codeInspection.ex;
18
19 import com.intellij.analysis.AnalysisScope;
20 import com.intellij.analysis.AnalysisUIOptions;
21 import com.intellij.analysis.PerformAnalysisInBackgroundOption;
22 import com.intellij.codeInsight.FileModificationService;
23 import com.intellij.codeInsight.daemon.ProblemHighlightFilter;
24 import com.intellij.codeInsight.daemon.impl.HighlightInfoProcessor;
25 import com.intellij.codeInsight.daemon.impl.LocalInspectionsPass;
26 import com.intellij.codeInspection.*;
27 import com.intellij.codeInspection.actions.CleanupInspectionIntention;
28 import com.intellij.codeInspection.lang.GlobalInspectionContextExtension;
29 import com.intellij.codeInspection.reference.RefElement;
30 import com.intellij.codeInspection.reference.RefEntity;
31 import com.intellij.codeInspection.reference.RefManagerImpl;
32 import com.intellij.codeInspection.reference.RefVisitor;
33 import com.intellij.codeInspection.ui.DefaultInspectionToolPresentation;
34 import com.intellij.codeInspection.ui.InspectionResultsView;
35 import com.intellij.codeInspection.ui.InspectionToolPresentation;
36 import com.intellij.codeInspection.ui.InspectionTreeState;
37 import com.intellij.concurrency.JobLauncher;
38 import com.intellij.concurrency.JobLauncherImpl;
39 import com.intellij.concurrency.SensitiveProgressWrapper;
40 import com.intellij.diagnostic.ThreadDumper;
41 import com.intellij.lang.annotation.ProblemGroup;
42 import com.intellij.lang.injection.InjectedLanguageManager;
43 import com.intellij.notification.NotificationGroup;
44 import com.intellij.openapi.Disposable;
45 import com.intellij.openapi.actionSystem.ToggleAction;
46 import com.intellij.openapi.application.*;
47 import com.intellij.openapi.application.ex.ApplicationManagerEx;
48 import com.intellij.openapi.components.PathMacroManager;
49 import com.intellij.openapi.diagnostic.Logger;
50 import com.intellij.openapi.editor.Document;
51 import com.intellij.openapi.progress.*;
52 import com.intellij.openapi.progress.impl.CoreProgressManager;
53 import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
54 import com.intellij.openapi.project.IndexNotReadyException;
55 import com.intellij.openapi.project.Project;
56 import com.intellij.openapi.project.ProjectUtil;
57 import com.intellij.openapi.project.ProjectUtilCore;
58 import com.intellij.openapi.roots.FileIndex;
59 import com.intellij.openapi.roots.ProjectRootManager;
60 import com.intellij.openapi.ui.MessageType;
61 import com.intellij.openapi.util.*;
62 import com.intellij.openapi.util.io.FileUtil;
63 import com.intellij.openapi.util.text.StringUtil;
64 import com.intellij.openapi.vfs.CharsetToolkit;
65 import com.intellij.openapi.vfs.VirtualFile;
66 import com.intellij.openapi.wm.ToolWindowId;
67 import com.intellij.openapi.wm.ToolWindowManager;
68 import com.intellij.psi.*;
69 import com.intellij.psi.search.LocalSearchScope;
70 import com.intellij.psi.search.SearchScope;
71 import com.intellij.psi.search.scope.packageSet.NamedScope;
72 import com.intellij.psi.util.PsiTreeUtil;
73 import com.intellij.psi.util.PsiUtilCore;
74 import com.intellij.ui.GuiUtils;
75 import com.intellij.ui.content.*;
76 import com.intellij.util.ConcurrencyUtil;
77 import com.intellij.util.IncorrectOperationException;
78 import com.intellij.util.Processor;
79 import com.intellij.util.TripleFunction;
80 import com.intellij.util.containers.ContainerUtil;
81 import com.intellij.util.containers.HashMap;
82 import com.intellij.util.containers.HashSet;
83 import com.intellij.util.ui.UIUtil;
84 import gnu.trove.THashSet;
85 import org.jdom.Element;
86 import org.jetbrains.annotations.NonNls;
87 import org.jetbrains.annotations.NotNull;
88 import org.jetbrains.annotations.Nullable;
89
90 import java.io.File;
91 import java.io.FileOutputStream;
92 import java.io.IOException;
93 import java.io.OutputStreamWriter;
94 import java.lang.reflect.Constructor;
95 import java.util.*;
96 import java.util.concurrent.*;
97 import java.util.stream.Collectors;
98 import java.util.stream.Stream;
99
100 public class GlobalInspectionContextImpl extends GlobalInspectionContextBase implements GlobalInspectionContext {
101   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.GlobalInspectionContextImpl");
102
103   public static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("Inspection Results", ToolWindowId.INSPECTION);
104
105   private final NotNullLazyValue<ContentManager> myContentManager;
106   private volatile InspectionResultsView myView;
107   private Content myContent;
108   private volatile boolean myViewClosed = true;
109   private long myInspectionStartedTimestamp;
110
111   @NotNull
112   private AnalysisUIOptions myUIOptions;
113   private InspectionTreeState myTreeState;
114
115   public GlobalInspectionContextImpl(@NotNull Project project, @NotNull NotNullLazyValue<ContentManager> contentManager) {
116     super(project);
117     myUIOptions = AnalysisUIOptions.getInstance(project).copy();
118     myContentManager = contentManager;
119   }
120
121   @NotNull
122   private ContentManager getContentManager() {
123     return myContentManager.getValue();
124   }
125
126   public void setTreeState(InspectionTreeState treeState) {
127     myTreeState = treeState;
128   }
129
130   public void addView(@NotNull InspectionResultsView view,
131                                    @NotNull String title,
132                                    boolean isOffline) {
133     LOG.assertTrue(myContent == null, "GlobalInspectionContext is busy under other view now");
134     myContentManager.getValue().addContentManagerListener(new ContentManagerAdapter() {
135       @Override
136       public void contentRemoved(ContentManagerEvent event) {
137         if (event.getContent() == myContent) {
138           if (myView != null) {
139             close(false);
140           }
141           myContent = null;
142         }
143       }
144     });
145
146     myView = view;
147     if (!isOffline) {
148       myView.setUpdating(true);
149     }
150     if (myTreeState != null) {
151       myView.getTree().setTreeState(myTreeState);
152     }
153     myContent = ContentFactory.SERVICE.getInstance().createContent(view, title, false);
154
155     myContent.setDisposer(myView);
156
157     ContentManager contentManager = getContentManager();
158     contentManager.addContent(myContent);
159     contentManager.setSelectedContent(myContent);
160
161     ToolWindowManager.getInstance(getProject()).getToolWindow(ToolWindowId.INSPECTION).activate(null);
162   }
163
164   public void addView(@NotNull InspectionResultsView view) {
165     addView(view, InspectionsBundle.message(view.isSingleInspectionRun() ?
166                                             "inspection.results.for.inspection.toolwindow.title" :
167                                             "inspection.results.for.profile.toolwindow.title",
168                                             view.getCurrentProfileName(), getCurrentScope().getShortenName()), false);
169
170   }
171
172   @Override
173   public void doInspections(@NotNull final AnalysisScope scope) {
174     if (myContent != null) {
175       getContentManager().removeContent(myContent, true);
176     }
177     super.doInspections(scope);
178   }
179
180   public void launchInspectionsOffline(@NotNull final AnalysisScope scope,
181                                        @Nullable final String outputPath,
182                                        final boolean runGlobalToolsOnly,
183                                        @NotNull final List<File> inspectionsResults) {
184     performInspectionsWithProgressAndExportResults(scope, runGlobalToolsOnly, true, outputPath, inspectionsResults);
185   }
186
187   public void performInspectionsWithProgressAndExportResults(@NotNull final AnalysisScope scope,
188                                                              final boolean runGlobalToolsOnly,
189                                                              final boolean isOfflineInspections,
190                                                              @Nullable final String outputPath,
191                                                              @NotNull final List<File> inspectionsResults) {
192     cleanupTools();
193     setCurrentScope(scope);
194
195     final Runnable action = () -> {
196       DefaultInspectionToolPresentation.setOutputPath(outputPath);
197       try {
198         performInspectionsWithProgress(scope, runGlobalToolsOnly, isOfflineInspections);
199         exportResults(inspectionsResults, outputPath);
200       }
201       finally {
202         DefaultInspectionToolPresentation.setOutputPath(null);
203       }
204     };
205     if (isOfflineInspections) {
206       ApplicationManager.getApplication().runReadAction(action);
207     }
208     else {
209       action.run();
210     }
211   }
212
213   private void exportResults(@NotNull List<File> inspectionsResults, @Nullable String outputPath) {
214     @NonNls final String ext = ".xml";
215     final Map<Element, Tools> globalTools = new HashMap<>();
216     for (Map.Entry<String,Tools> entry : getTools().entrySet()) {
217       final Tools sameTools = entry.getValue();
218       boolean hasProblems = false;
219       String toolName = entry.getKey();
220       if (sameTools != null) {
221         for (ScopeToolState toolDescr : sameTools.getTools()) {
222           InspectionToolWrapper toolWrapper = toolDescr.getTool();
223           if (toolWrapper instanceof LocalInspectionToolWrapper) {
224             hasProblems = new File(outputPath, toolName + ext).exists();
225           }
226           else {
227             InspectionToolPresentation presentation = getPresentation(toolWrapper);
228             presentation.updateContent();
229             if (presentation.hasReportedProblems()) {
230               final Element root = new Element(InspectionsBundle.message("inspection.problems"));
231               globalTools.put(root, sameTools);
232               LOG.assertTrue(!hasProblems, toolName);
233               break;
234             }
235           }
236         }
237       }
238       if (hasProblems) {
239         try {
240           new File(outputPath).mkdirs();
241           final File file = new File(outputPath, toolName + ext);
242           inspectionsResults.add(file);
243           FileUtil
244             .writeToFile(file, ("</" + InspectionsBundle.message("inspection.problems") + ">").getBytes(CharsetToolkit.UTF8_CHARSET), true);
245         }
246         catch (IOException e) {
247           LOG.error(e);
248         }
249       }
250     }
251
252     getRefManager().iterate(new RefVisitor() {
253       @Override
254       public void visitElement(@NotNull final RefEntity refEntity) {
255         for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) {
256           Tools tools = entry.getValue();
257           Element element = entry.getKey();
258           for (ScopeToolState state : tools.getTools()) {
259             try {
260               InspectionToolWrapper toolWrapper = state.getTool();
261               InspectionToolPresentation presentation = getPresentation(toolWrapper);
262               presentation.exportResults(element, refEntity, d -> false);
263             }
264             catch (Throwable e) {
265               LOG.error("Problem when exporting: " + refEntity.getExternalName(), e);
266             }
267           }
268         }
269       }
270     });
271
272     for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) {
273       final String toolName = entry.getValue().getShortName();
274       Element element = entry.getKey();
275       element.setAttribute(LOCAL_TOOL_ATTRIBUTE, Boolean.toString(false));
276       final org.jdom.Document doc = new org.jdom.Document(element);
277       PathMacroManager.getInstance(getProject()).collapsePaths(doc.getRootElement());
278       try {
279         new File(outputPath).mkdirs();
280         final File file = new File(outputPath, toolName + ext);
281         inspectionsResults.add(file);
282
283         try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), CharsetToolkit.UTF8_CHARSET)) {
284           JDOMUtil.writeDocument(doc, writer, "\n");
285         }
286       }
287       catch (IOException e) {
288         LOG.error(e);
289       }
290     }
291   }
292
293   public void ignoreElement(@NotNull InspectionProfileEntry tool, @NotNull PsiElement element) {
294     final RefElement refElement = getRefManager().getReference(element);
295     final Tools tools = getTools().get(tool.getShortName());
296     if (tools != null){
297       for (ScopeToolState state : tools.getTools()) {
298         InspectionToolWrapper toolWrapper = state.getTool();
299         ignoreElementRecursively(toolWrapper, refElement);
300       }
301     }
302   }
303
304   public InspectionResultsView getView() {
305     return myView;
306   }
307
308   private void ignoreElementRecursively(@NotNull InspectionToolWrapper toolWrapper, final RefEntity refElement) {
309     if (refElement != null) {
310       InspectionToolPresentation presentation = getPresentation(toolWrapper);
311       presentation.ignoreCurrentElement(refElement);
312       final List<RefEntity> children = refElement.getChildren();
313       for (RefEntity child : children) {
314         ignoreElementRecursively(toolWrapper, child);
315       }
316     }
317   }
318
319   @NotNull
320   public AnalysisUIOptions getUIOptions() {
321     return myUIOptions;
322   }
323
324   public void setSplitterProportion(final float proportion) {
325     myUIOptions.SPLITTER_PROPORTION = proportion;
326   }
327
328   @NotNull
329   public ToggleAction createToggleAutoscrollAction() {
330     return myUIOptions.getAutoScrollToSourceHandler().createToggleAction();
331   }
332
333   @Override
334   protected void launchInspections(@NotNull final AnalysisScope scope) {
335     if (!ApplicationManager.getApplication().isUnitTestMode()) {
336       myUIOptions = AnalysisUIOptions.getInstance(getProject()).copy();
337     }
338     myViewClosed = false;
339     super.launchInspections(scope);
340   }
341
342   @NotNull
343   @Override
344   protected PerformInBackgroundOption createOption() {
345     return new PerformAnalysisInBackgroundOption(getProject());
346   }
347
348   @Override
349   protected void notifyInspectionsFinished(@NotNull final AnalysisScope scope) {
350     if (ApplicationManager.getApplication().isUnitTestMode()) return;
351     UIUtil.invokeLaterIfNeeded(() -> {
352       long elapsed = System.currentTimeMillis() - myInspectionStartedTimestamp;
353       LOG.info("Code inspection finished. Took "+elapsed+"ms");
354       if (getProject().isDisposed()) return;
355
356       InspectionResultsView view = myView == null ? new InspectionResultsView(this, createContentProvider()) : null;
357       if (!(myView == null ? view : myView).hasProblems()) {
358         NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message",
359                                                                         scope.getFileCount(),
360                                                                         scope.getShortenName()),
361                                               MessageType.INFO).notify(getProject());
362         close(true);
363         if (view != null) {
364           Disposer.dispose(view);
365         }
366       }
367       else if (view != null && !view.isDisposed() && getCurrentScope() != null) {
368         addView(view);
369         view.update();
370       }
371       if (myView != null) {
372         myView.setUpdating(false);
373       }
374     });
375   }
376
377   @Override
378   protected void runTools(@NotNull final AnalysisScope scope, boolean runGlobalToolsOnly, boolean isOfflineInspections) {
379     myInspectionStartedTimestamp = System.currentTimeMillis();
380     final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
381     if (progressIndicator == null) {
382       throw new IncorrectOperationException("Must be run under progress");
383     }
384     if (!isOfflineInspections && ApplicationManager.getApplication().isDispatchThread()) {
385       throw new IncorrectOperationException("Must not start inspections from within EDT");
386     }
387     if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
388       throw new IncorrectOperationException("Must not start inspections from within write action");
389     }
390     // in offline inspection application we don't care about global read action
391     if (!isOfflineInspections && ApplicationManager.getApplication().isReadAccessAllowed()) {
392       throw new IncorrectOperationException("Must not start inspections from within global read action");
393     }
394     final InspectionManager inspectionManager = InspectionManager.getInstance(getProject());
395     ((RefManagerImpl)getRefManager()).initializeAnnotators();
396     final List<Tools> globalTools = new ArrayList<>();
397     final List<Tools> localTools = new ArrayList<>();
398     final List<Tools> globalSimpleTools = new ArrayList<>();
399     initializeTools(globalTools, localTools, globalSimpleTools);
400     appendPairedInspectionsForUnfairTools(globalTools, globalSimpleTools, localTools);
401
402     runGlobalTools(scope, inspectionManager, globalTools, isOfflineInspections);
403
404     if (runGlobalToolsOnly || localTools.isEmpty() && globalSimpleTools.isEmpty()) return;
405
406     SearchScope searchScope = ReadAction.compute(scope::toSearchScope);
407     final Set<VirtualFile> localScopeFiles = searchScope instanceof LocalSearchScope ? new THashSet<>() : null;
408     for (Tools tools : globalSimpleTools) {
409       GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool();
410       GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
411       tool.inspectionStarted(inspectionManager, this, getPresentation(toolWrapper));
412     }
413
414     final boolean headlessEnvironment = ApplicationManager.getApplication().isHeadlessEnvironment();
415     final Map<String, InspectionToolWrapper> map = getInspectionWrappersMap(localTools);
416
417     final BlockingQueue<PsiFile> filesToInspect = new ArrayBlockingQueue<>(1000);
418     // use original progress indicator here since we don't want it to cancel on write action start
419     ProgressIndicator iteratingIndicator = new SensitiveProgressWrapper(progressIndicator);
420     Future<?> future = startIterateScopeInBackground(scope, localScopeFiles, headlessEnvironment, filesToInspect, iteratingIndicator);
421
422     Processor<PsiFile> processor = file -> {
423       ProgressManager.checkCanceled();
424       if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(() -> {
425         if (!file.isValid()) {
426           return;
427         }
428         VirtualFile virtualFile = file.getVirtualFile();
429         if (!scope.contains(virtualFile)) {
430           LOG.error(file.getName()+"; scope: "+scope+"; "+virtualFile);
431         }
432         inspectFile(file, getEffectiveRange(searchScope, file), inspectionManager, localTools, globalSimpleTools, map);
433       })) {
434         throw new ProcessCanceledException();
435       }
436
437       boolean includeDoNotShow = includeDoNotShow(getCurrentProfile());
438       Stream.concat(getWrappersFromTools(localTools, file, includeDoNotShow).stream(),
439                     getWrappersFromTools(globalSimpleTools, file, includeDoNotShow).stream())
440         .filter(wrapper -> wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)
441         .forEach(wrapper -> {
442           ProblemDescriptor[] descriptors = ((ExternalAnnotatorBatchInspection)wrapper.getTool()).checkFile(file, this, inspectionManager);
443           InspectionToolPresentation toolPresentation = getPresentation(wrapper);
444           ReadAction.run(() -> LocalDescriptorsUtil.addProblemDescriptors(Arrays.asList(descriptors), false, this, null, CONVERT, toolPresentation));
445         });
446
447       return true;
448     };
449     try {
450       final Queue<PsiFile> filesFailedToInspect = new LinkedBlockingQueue<>();
451       while (true) {
452         Disposable disposable = Disposer.newDisposable();
453         ProgressIndicator wrapper = new SensitiveProgressWrapper(progressIndicator);
454
455         try {
456           // avoid "attach listener"/"write action" race
457           ReadAction.run(() -> {
458             wrapper.start();
459             ProgressIndicatorUtils.forceWriteActionPriority(wrapper, disposable);
460             // there is a chance we are racing with write action, in which case just registered listener might not be called, retry.
461             if (ApplicationManagerEx.getApplicationEx().isWriteActionPending()) {
462               throw new ProcessCanceledException();
463             }
464           });
465           // use wrapper here to cancel early when write action start but do not affect the original indicator
466           ((JobLauncherImpl)JobLauncher.getInstance()).processQueue(filesToInspect, filesFailedToInspect, wrapper, TOMBSTONE, processor);
467           break;
468         }
469         catch (ProcessCanceledException ignored) {
470           progressIndicator.checkCanceled();
471           // PCE may be thrown from inside wrapper when write action started
472           // go on with the write and then resume processing the rest of the queue
473           assert !ApplicationManager.getApplication().isReadAccessAllowed();
474           assert !ApplicationManager.getApplication().isDispatchThread();
475
476           // wait for write action to complete
477           ApplicationManager.getApplication().runReadAction(EmptyRunnable.getInstance());
478         }
479         finally {
480           Disposer.dispose(disposable);
481         }
482       }
483     }
484     finally {
485       iteratingIndicator.cancel(); // tell file scanning thread to stop
486       filesToInspect.clear(); // let file scanning thread a chance to put TOMBSTONE and complete
487       try {
488         future.get(30, TimeUnit.SECONDS);
489       }
490       catch (Exception e) {
491         LOG.error("Thread dump: \n"+ThreadDumper.dumpThreadsToString(), e);
492       }
493     }
494
495     ProgressManager.checkCanceled();
496
497     for (Tools tools : globalSimpleTools) {
498       GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool();
499       GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
500       ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, map);
501       tool.inspectionFinished(inspectionManager, this, problemDescriptionProcessor);
502
503     }
504
505     addProblemsToView(globalSimpleTools);
506   }
507
508   private static TextRange getEffectiveRange(SearchScope searchScope, PsiFile file) {
509     if (searchScope instanceof LocalSearchScope) {
510       PsiElement[] scopeFileElements = Arrays.stream(((LocalSearchScope)searchScope).getScope())
511         .filter(e -> e.getContainingFile() == file)
512         .toArray(PsiElement[]::new);
513       if (scopeFileElements.length > 0) {
514         int start = -1;
515         int end = -1;
516         for (PsiElement scopeElement : scopeFileElements) {
517           TextRange elementRange = scopeElement.getTextRange();
518           start = start == -1 ? elementRange.getStartOffset() : Math.min(elementRange.getStartOffset(), start);
519           end = end == -1 ? elementRange.getEndOffset() : Math.max(elementRange.getEndOffset(), end);
520         }
521         return new TextRange(start, end);
522       }
523     }
524     return new TextRange(0, file.getTextLength());
525   }
526
527   private void inspectFile(@NotNull final PsiFile file,
528                            @NotNull final TextRange range,
529                            @NotNull final InspectionManager inspectionManager,
530                            @NotNull List<Tools> localTools,
531                            @NotNull List<Tools> globalSimpleTools,
532                            @NotNull final Map<String, InspectionToolWrapper> wrappersMap) {
533     Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file);
534     if (document == null) return;
535
536     VirtualFile virtualFile = file.getVirtualFile();
537     String url = ProjectUtilCore.displayUrlRelativeToProject(virtualFile, virtualFile.getPresentableUrl(), getProject(), true, false);
538     incrementJobDoneAmount(getStdJobDescriptors().LOCAL_ANALYSIS, url);
539
540     final LocalInspectionsPass pass = new LocalInspectionsPass(file, document, range.getStartOffset(),
541                                                                range.getEndOffset(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true,
542                                                                HighlightInfoProcessor.getEmpty());
543     try {
544       boolean includeDoNotShow = includeDoNotShow(getCurrentProfile());
545       final List<LocalInspectionToolWrapper> lTools = getWrappersFromTools(localTools, file, includeDoNotShow);
546       List<LocalInspectionToolWrapper> nonExternalAnnotators = lTools.stream().filter(wrapper -> !(wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)).collect(Collectors.toList());
547       pass.doInspectInBatch(this, inspectionManager, nonExternalAnnotators);
548
549       List<GlobalInspectionToolWrapper> globalSTools = getWrappersFromTools(globalSimpleTools, file, includeDoNotShow);
550       final List<GlobalInspectionToolWrapper> tools = globalSTools.stream()
551         .filter(wrapper -> !(wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)).collect(Collectors.toList());
552       JobLauncher.getInstance().invokeConcurrentlyUnderProgress(tools, myProgressIndicator, false, toolWrapper -> {
553         GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
554         ProblemsHolder holder = new ProblemsHolder(inspectionManager, file, false);
555         ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, wrappersMap);
556         tool.checkFile(file, inspectionManager, holder, this, problemDescriptionProcessor);
557         InspectionToolPresentation toolPresentation = getPresentation(toolWrapper);
558         LocalDescriptorsUtil.addProblemDescriptors(holder.getResults(), false, this, null, CONVERT, toolPresentation);
559         return true;
560       });
561     }
562     catch (ProcessCanceledException e) {
563       final Throwable cause = e.getCause();
564       if (cause == null) {
565         throw e;
566       }
567       LOG.error("In file: " + file, cause);
568     }
569     catch (IndexNotReadyException e) {
570       throw e;
571     }
572     catch (Throwable e) {
573       LOG.error("In file: " + file.getName(), e);
574     }
575     finally {
576       InjectedLanguageManager.getInstance(getProject()).dropFileCaches(file);
577     }
578   }
579
580   protected boolean includeDoNotShow(final InspectionProfile profile) {
581     return profile.getSingleTool() != null;
582   }
583
584   private static final PsiFile TOMBSTONE = PsiUtilCore.NULL_PSI_FILE;
585
586   @NotNull
587   private Future<?> startIterateScopeInBackground(@NotNull final AnalysisScope scope,
588                                                   @Nullable final Collection<VirtualFile> localScopeFiles,
589                                                   final boolean headlessEnvironment,
590                                                   @NotNull final BlockingQueue<PsiFile> outFilesToInspect,
591                                                   @NotNull final ProgressIndicator progressIndicator) {
592     Task.Backgroundable task = new Task.Backgroundable(getProject(), "Scanning Files to Inspect") {
593       @Override
594       public void run(@NotNull ProgressIndicator indicator) {
595         try {
596           final FileIndex fileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex();
597           scope.accept(file -> {
598             ProgressManager.checkCanceled();
599             if (ProjectUtil.isProjectOrWorkspaceFile(file) || !fileIndex.isInContent(file)) return true;
600
601             PsiFile psiFile = ReadAction.compute(() -> {
602               if (getProject().isDisposed()) throw new ProcessCanceledException();
603               PsiFile psi = PsiManager.getInstance(getProject()).findFile(file);
604               Document document = psi == null ? null : shouldProcess(psi, headlessEnvironment, localScopeFiles);
605               if (document != null) {
606                 return psi;
607               }
608               return null;
609             });
610             // do not inspect binary files
611             if (psiFile != null) {
612               try {
613                 if (ApplicationManager.getApplication().isReadAccessAllowed()) {
614                   throw new IllegalStateException("Must not have read action");
615                 }
616                 outFilesToInspect.put(psiFile);
617               }
618               catch (InterruptedException e) {
619                 LOG.error(e);
620               }
621             }
622             ProgressManager.checkCanceled();
623             return true;
624           });
625         }
626         catch (ProcessCanceledException e) {
627           // ignore, but put tombstone
628         }
629         finally {
630           try {
631             outFilesToInspect.put(TOMBSTONE);
632           }
633           catch (InterruptedException e) {
634             LOG.error(e);
635           }
636         }
637       }
638     };
639     return ((CoreProgressManager)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(task, progressIndicator, null);
640   }
641
642   private Document shouldProcess(@NotNull PsiFile file, boolean headlessEnvironment, @Nullable Collection<VirtualFile> localScopeFiles) {
643     final VirtualFile virtualFile = file.getVirtualFile();
644     if (virtualFile == null) return null;
645     if (isBinary(file)) return null; //do not inspect binary files
646
647     if (myViewClosed && !headlessEnvironment) {
648       throw new ProcessCanceledException();
649     }
650
651     if (LOG.isDebugEnabled()) {
652       LOG.debug("Running local inspections on " + virtualFile.getPath());
653     }
654
655     if (SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile)) return null;
656     if (localScopeFiles != null && !localScopeFiles.add(virtualFile)) return null;
657     if (!ProblemHighlightFilter.shouldProcessFileInBatch(file)) return null;
658
659     return PsiDocumentManager.getInstance(getProject()).getDocument(file);
660   }
661
662   private void runGlobalTools(@NotNull final AnalysisScope scope,
663                               @NotNull final InspectionManager inspectionManager,
664                               @NotNull List<Tools> globalTools,
665                               boolean isOfflineInspections) {
666     LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed() || isOfflineInspections, "Must not run under read action, too unresponsive");
667     final List<InspectionToolWrapper> needRepeatSearchRequest = new ArrayList<>();
668
669     final boolean canBeExternalUsages = !(scope.getScopeType() == AnalysisScope.PROJECT && scope.isIncludeTestSource());
670     for (Tools tools : globalTools) {
671       for (ScopeToolState state : tools.getTools()) {
672         final InspectionToolWrapper toolWrapper = state.getTool();
673         final GlobalInspectionTool tool = (GlobalInspectionTool)toolWrapper.getTool();
674         final InspectionToolPresentation toolPresentation = getPresentation(toolWrapper);
675         try {
676           if (tool.isGraphNeeded()) {
677             try {
678               ((RefManagerImpl)getRefManager()).findAllDeclarations();
679             }
680             catch (Throwable e) {
681               getStdJobDescriptors().BUILD_GRAPH.setDoneAmount(0);
682               throw e;
683             }
684           }
685           ApplicationManager.getApplication().runReadAction(() -> {
686             tool.runInspection(scope, inspectionManager, this, toolPresentation);
687             //skip phase when we are sure that scope already contains everything, unused declaration though needs to proceed with its suspicious code
688             if ((canBeExternalUsages || tool.getAdditionalJobs(this) != null) &&
689                 tool.queryExternalUsagesRequests(inspectionManager, this, toolPresentation)) {
690               needRepeatSearchRequest.add(toolWrapper);
691             }
692           });
693         }
694         catch (ProcessCanceledException | IndexNotReadyException e) {
695           throw e;
696         }
697         catch (Throwable e) {
698           LOG.error(e);
699         }
700       }
701     }
702
703     for (GlobalInspectionContextExtension extension : myExtensions.values()) {
704       try {
705         extension.performPostRunActivities(needRepeatSearchRequest, this);
706       }
707       catch (ProcessCanceledException | IndexNotReadyException e) {
708         throw e;
709       }
710       catch (Throwable e) {
711         LOG.error(e);
712       }
713     }
714
715     addProblemsToView(globalTools);
716   }
717
718   public ActionCallback initializeViewIfNeed() {
719     if (myView != null) {
720       return ActionCallback.DONE;
721     }
722     final Application app = ApplicationManager.getApplication();
723     final Runnable createView = () -> {
724       InspectionResultsView view = getView();
725       if (view == null) {
726         view = new InspectionResultsView(this, createContentProvider());
727         addView(view);
728       }
729     };
730     if (app.isUnitTestMode()) {
731       createView.run();
732       return ActionCallback.DONE;
733     } else {
734       return app.getInvokator().invokeLater(createView);
735     }
736   }
737
738   private void appendPairedInspectionsForUnfairTools(@NotNull List<Tools> globalTools,
739                                                      @NotNull List<Tools> globalSimpleTools,
740                                                      @NotNull List<Tools> localTools) {
741     Tools[] larray = localTools.toArray(new Tools[localTools.size()]);
742     for (Tools tool : larray) {
743       LocalInspectionToolWrapper toolWrapper = (LocalInspectionToolWrapper)tool.getTool();
744       LocalInspectionTool localTool = toolWrapper.getTool();
745       if (localTool instanceof PairedUnfairLocalInspectionTool) {
746         String batchShortName = ((PairedUnfairLocalInspectionTool)localTool).getInspectionForBatchShortName();
747         InspectionProfile currentProfile = getCurrentProfile();
748         InspectionToolWrapper batchInspection;
749         if (currentProfile == null) {
750           batchInspection = null;
751         }
752         else {
753           final InspectionToolWrapper pairedWrapper = currentProfile.getInspectionTool(batchShortName, getProject());
754           batchInspection = pairedWrapper != null ? pairedWrapper.createCopy() : null;
755         }
756         if (batchInspection != null && !getTools().containsKey(batchShortName)) {
757           // add to existing inspections to run
758           InspectionProfileEntry batchTool = batchInspection.getTool();
759           final ScopeToolState defaultState = tool.getDefaultState();
760           ToolsImpl newTool = new ToolsImpl(batchInspection, defaultState.getLevel(), true, defaultState.isEnabled());
761           for (ScopeToolState state : tool.getTools()) {
762             final NamedScope scope = state.getScope(getProject());
763             if (scope != null) {
764               newTool.addTool(scope, batchInspection, state.isEnabled(), state.getLevel());
765             }
766           }
767           if (batchTool instanceof LocalInspectionTool) localTools.add(newTool);
768           else if (batchTool instanceof GlobalSimpleInspectionTool) globalSimpleTools.add(newTool);
769           else if (batchTool instanceof GlobalInspectionTool) globalTools.add(newTool);
770           else throw new AssertionError(batchTool);
771           myTools.put(batchShortName, newTool);
772           batchInspection.initialize(this);
773         }
774       }
775     }
776   }
777
778   @NotNull
779   private static <T extends InspectionToolWrapper> List<T> getWrappersFromTools(@NotNull List<Tools> localTools,
780                                                                                 @NotNull PsiFile file,
781                                                                                 boolean includeDoNotShow) {
782     final List<T> lTools = new ArrayList<>();
783     for (Tools tool : localTools) {
784       //noinspection unchecked
785       final T enabledTool = (T)tool.getEnabledTool(file, includeDoNotShow);
786       if (enabledTool != null) {
787         lTools.add(enabledTool);
788       }
789     }
790     return lTools;
791   }
792
793   @NotNull
794   private ProblemDescriptionsProcessor getProblemDescriptionProcessor(@NotNull final GlobalInspectionToolWrapper toolWrapper,
795                                                                       @NotNull final Map<String, InspectionToolWrapper> wrappersMap) {
796     return new ProblemDescriptionsProcessor() {
797       @Override
798       public void addProblemElement(@Nullable RefEntity refEntity, @NotNull CommonProblemDescriptor... commonProblemDescriptors) {
799         for (CommonProblemDescriptor problemDescriptor : commonProblemDescriptors) {
800           if (!(problemDescriptor instanceof ProblemDescriptor)) {
801             continue;
802           }
803           ProblemGroup problemGroup = ((ProblemDescriptor)problemDescriptor).getProblemGroup();
804
805           InspectionToolWrapper targetWrapper = problemGroup == null ? toolWrapper : wrappersMap.get(problemGroup.getProblemName());
806           if (targetWrapper != null) { // Else it's switched off
807             InspectionToolPresentation toolPresentation = getPresentation(targetWrapper);
808             toolPresentation.addProblemElement(refEntity, problemDescriptor);
809           }
810         }
811       }
812     };
813   }
814
815   @NotNull
816   private static Map<String, InspectionToolWrapper> getInspectionWrappersMap(@NotNull List<Tools> tools) {
817     Map<String, InspectionToolWrapper> name2Inspection = new HashMap<>(tools.size());
818     for (Tools tool : tools) {
819       InspectionToolWrapper toolWrapper = tool.getTool();
820       name2Inspection.put(toolWrapper.getShortName(), toolWrapper);
821     }
822
823     return name2Inspection;
824   }
825
826   private static final TripleFunction<LocalInspectionTool,PsiElement,GlobalInspectionContext,RefElement> CONVERT =
827     (tool, elt, context) -> {
828       PsiNamedElement problemElement = PsiTreeUtil.getNonStrictParentOfType(elt, PsiFile.class);
829
830       RefElement refElement = context.getRefManager().getReference(problemElement);
831       if (refElement == null && problemElement != null) {  // no need to lose collected results
832         refElement = GlobalInspectionContextUtil.retrieveRefElement(elt, context);
833       }
834       return refElement;
835     };
836
837
838   @Override
839   public void close(boolean noSuspiciousCodeFound) {
840     if (!noSuspiciousCodeFound) {
841       if (myView.isRerun()) {
842         myViewClosed = true;
843         myView = null;
844       }
845       if (myView == null) {
846         return;
847       }
848     }
849     AnalysisUIOptions.getInstance(getProject()).save(myUIOptions);
850     if (myContent != null) {
851       final ContentManager contentManager = getContentManager();
852       contentManager.removeContent(myContent, true);
853     }
854     myViewClosed = true;
855     myView = null;
856     ((InspectionManagerEx)InspectionManager.getInstance(getProject())).closeRunningContext(this);
857     for (Tools tools : getTools().values()) {
858       for (ScopeToolState state : tools.getTools()) {
859         InspectionToolWrapper toolWrapper = state.getTool();
860         getPresentation(toolWrapper).finalCleanup();
861       }
862     }
863     super.close(noSuspiciousCodeFound);
864   }
865
866   @Override
867   public void cleanup() {
868     if (myView != null) {
869       myView.setUpdating(false);
870     } else {
871       myPresentationMap.clear();
872       super.cleanup();
873     }
874   }
875
876   public void refreshViews() {
877     if (myView != null) {
878       myView.getTree().queueUpdate();
879     }
880   }
881
882   private final ConcurrentMap<InspectionToolWrapper, InspectionToolPresentation> myPresentationMap = ContainerUtil.newConcurrentMap();
883   @NotNull
884   public InspectionToolPresentation getPresentation(@NotNull InspectionToolWrapper toolWrapper) {
885     InspectionToolPresentation presentation = myPresentationMap.get(toolWrapper);
886     if (presentation == null) {
887       String presentationClass = StringUtil.notNullize(toolWrapper.myEP == null ? null : toolWrapper.myEP.presentation, DefaultInspectionToolPresentation.class.getName());
888
889       try {
890         Constructor<?> constructor = Class.forName(presentationClass).getConstructor(InspectionToolWrapper.class, GlobalInspectionContextImpl.class);
891         presentation = (InspectionToolPresentation)constructor.newInstance(toolWrapper, this);
892       }
893       catch (Exception e) {
894         LOG.error(e);
895         throw new RuntimeException(e);
896       }
897       presentation = ConcurrencyUtil.cacheOrGet(myPresentationMap, toolWrapper, presentation);
898     }
899     return presentation;
900   }
901
902   @Override
903   public void codeCleanup(@NotNull final AnalysisScope scope,
904                           @NotNull final InspectionProfile profile,
905                           @Nullable final String commandName,
906                           @Nullable final Runnable postRunnable,
907                           final boolean modal) {
908     String title = "Inspect Code...";
909     Task task = modal ? new Task.Modal(getProject(), title, true) {
910       @Override
911       public void run(@NotNull ProgressIndicator indicator) {
912         cleanup(scope, profile, postRunnable, commandName);
913       }
914     } : new Task.Backgroundable(getProject(), title, true) {
915       @Override
916       public void run(@NotNull ProgressIndicator indicator) {
917         cleanup(scope, profile, postRunnable, commandName);
918       }
919     };
920     ProgressManager.getInstance().run(task);
921   }
922
923   private void cleanup(@NotNull final AnalysisScope scope,
924                        @NotNull InspectionProfile profile,
925                        @Nullable final Runnable postRunnable,
926                        @Nullable final String commandName) {
927     setCurrentScope(scope);
928     final int fileCount = scope.getFileCount();
929     final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
930
931     final SearchScope searchScope = ReadAction.compute(scope::toSearchScope);
932     final TextRange range;
933     if (searchScope instanceof LocalSearchScope) {
934       final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope();
935       range = elements.length == 1 ? ReadAction.compute(elements[0]::getTextRange) : null;
936     }
937     else {
938       range = null;
939     }
940     final Iterable<Tools> inspectionTools = ContainerUtil.filter(profile.getAllEnabledInspectionTools(getProject()), tools -> {
941       assert tools != null;
942       return tools.getTool().isCleanupTool();
943     });
944     boolean includeDoNotShow = includeDoNotShow(profile);
945     final RefManagerImpl refManager = (RefManagerImpl)getRefManager();
946     refManager.inspectionReadActionStarted();
947     List<ProblemDescriptor> descriptors = new ArrayList<>();
948     Set<PsiFile> files = new HashSet<>();
949     try {
950       scope.accept(new PsiElementVisitor() {
951         private int myCount;
952         @Override
953         public void visitFile(PsiFile file) {
954           if (progressIndicator != null) {
955             progressIndicator.setFraction((double)++myCount / fileCount);
956           }
957           if (isBinary(file)) return;
958           final List<LocalInspectionToolWrapper> lTools = new ArrayList<>();
959           for (final Tools tools : inspectionTools) {
960             InspectionToolWrapper tool = tools.getEnabledTool(file, includeDoNotShow);
961             if (tool instanceof GlobalInspectionToolWrapper) {
962               tool = ((GlobalInspectionToolWrapper)tool).getSharedLocalInspectionToolWrapper();
963             }
964             if (tool != null) {
965               lTools.add((LocalInspectionToolWrapper)tool);
966               tool.initialize(GlobalInspectionContextImpl.this);
967             }
968           }
969
970           if (!lTools.isEmpty()) {
971             try {
972               final LocalInspectionsPass pass = new LocalInspectionsPass(file, PsiDocumentManager.getInstance(getProject()).getDocument(file), range != null ? range.getStartOffset() : 0,
973                                                                          range != null ? range.getEndOffset() : file.getTextLength(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true,
974                                                                          HighlightInfoProcessor.getEmpty());
975               Runnable runnable = () -> pass.doInspectInBatch(GlobalInspectionContextImpl.this, InspectionManager.getInstance(getProject()), lTools);
976               ApplicationManager.getApplication().runReadAction(runnable);
977
978               final Set<ProblemDescriptor> localDescriptors = new TreeSet<>(CommonProblemDescriptor.DESCRIPTOR_COMPARATOR);
979               for (LocalInspectionToolWrapper tool : lTools) {
980                 InspectionToolPresentation toolPresentation = getPresentation(tool);
981                 for (CommonProblemDescriptor descriptor : toolPresentation.getProblemDescriptors()) {
982                   if (descriptor instanceof ProblemDescriptor) {
983                     localDescriptors.add((ProblemDescriptor)descriptor);
984                   }
985                 }
986               }
987
988               if (searchScope instanceof LocalSearchScope) {
989                 for (Iterator<ProblemDescriptor> iterator = localDescriptors.iterator(); iterator.hasNext(); ) {
990                   final ProblemDescriptor descriptor = iterator.next();
991                   final TextRange infoRange = descriptor instanceof ProblemDescriptorBase ? ((ProblemDescriptorBase)descriptor).getTextRange() : null;
992                   if (infoRange != null && !((LocalSearchScope)searchScope).containsRange(file, infoRange)) {
993                     iterator.remove();
994                   }
995                 }
996               }
997               if (!localDescriptors.isEmpty()) {
998                 descriptors.addAll(localDescriptors);
999                 files.add(file);
1000               }
1001             }
1002             finally {
1003               myPresentationMap.clear();
1004             }
1005           }
1006         }
1007       });
1008     }
1009     finally {
1010       refManager.inspectionReadActionFinished();
1011     }
1012
1013     if (files.isEmpty()) {
1014       GuiUtils.invokeLaterIfNeeded(() -> {
1015         if (commandName != null) {
1016           NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message", scope.getFileCount(), scope.getDisplayName()), MessageType.INFO).notify(getProject());
1017         }
1018         if (postRunnable != null) {
1019           postRunnable.run();
1020         }
1021       }, ModalityState.defaultModalityState());
1022       return;
1023     }
1024
1025     Runnable runnable = () -> {
1026       if (!FileModificationService.getInstance().preparePsiElementsForWrite(files)) return;
1027       CleanupInspectionIntention.applyFixesNoSort(getProject(), "Code Cleanup", descriptors, null);
1028       if (postRunnable != null) {
1029         postRunnable.run();
1030       }
1031     };
1032     TransactionGuard.submitTransaction(getProject(), runnable);
1033   }
1034
1035   private static boolean isBinary(@NotNull PsiFile file) {
1036     return file instanceof PsiBinaryFile || file.getFileType().isBinary();
1037   }
1038
1039   public boolean isViewClosed() {
1040     return myViewClosed;
1041   }
1042
1043   private InspectionRVContentProvider createContentProvider() {
1044     return new InspectionRVContentProviderImpl(getProject());
1045   }
1046
1047   private void addProblemsToView(List<Tools> tools) {
1048     if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
1049       return;
1050     }
1051     if (myView == null && !ReadAction.compute(() -> InspectionResultsView.hasProblems(tools, this, createContentProvider())).booleanValue()) {
1052       return;
1053     }
1054     initializeViewIfNeed().doWhenDone(() -> myView.addTools(tools));
1055   }
1056 }