From eb330fdea54e6eb24465e4bf5edcc31619ffecf9 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Wed, 14 Dec 2016 12:47:24 +0300 Subject: [PATCH] IDEA-165197 NullPointerException in IndentsPass may break plugins --- .../intellij/openapi/editor/RangeMarker.java | 10 +---- .../codeInsight/daemon/impl/IndentsPass.java | 38 +++++++---------- .../com/intellij/openapi/util/Segment.java | 11 +++++ .../ToggleHighlightingMarkupAction.java | 42 ++++++++----------- 4 files changed, 44 insertions(+), 57 deletions(-) diff --git a/platform/core-api/src/com/intellij/openapi/editor/RangeMarker.java b/platform/core-api/src/com/intellij/openapi/editor/RangeMarker.java index 820b7b768355..45b1b887dfea 100644 --- a/platform/core-api/src/com/intellij/openapi/editor/RangeMarker.java +++ b/platform/core-api/src/com/intellij/openapi/editor/RangeMarker.java @@ -81,15 +81,7 @@ public interface RangeMarker extends UserDataHolder, Segment { */ void setGreedyToRight(boolean greedy); - Comparator BY_START_OFFSET = new Comparator() { - @Override - public int compare(RangeMarker r1, RangeMarker r2) { - int result = r1.getStartOffset() - r2.getStartOffset(); - if (result == 0) result = r1.getEndOffset() - r2.getEndOffset(); - return result; - } - }; - + Comparator BY_START_OFFSET = BY_START_OFFSET_THEN_END_OFFSET; boolean isGreedyToRight(); boolean isGreedyToLeft(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java index 7fd35030b13a..262f83e98349 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java @@ -40,6 +40,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; @@ -56,17 +57,13 @@ import java.util.List; public class IndentsPass extends TextEditorHighlightingPass implements DumbAware { private static final Key> INDENT_HIGHLIGHTERS_IN_EDITOR_KEY = Key.create("INDENT_HIGHLIGHTERS_IN_EDITOR_KEY"); - private static final Key LAST_TIME_INDENTS_BUILT = Key.create("LAST_TIME_INDENTS_BUILT"); + private static final Key LAST_TIME_INDENTS_BUILT = Key.create("LAST_TIME_INDENTS_BUILT"); private final EditorEx myEditor; private final PsiFile myFile; - public static final Comparator RANGE_COMPARATOR = (o1, o2) -> { - if (o1.getStartOffset() == o2.getStartOffset()) { - return o1.getEndOffset() - o2.getEndOffset(); - } - return o1.getStartOffset() - o2.getStartOffset(); - }; + private volatile List myRanges = Collections.emptyList(); + private volatile List myDescriptors = Collections.emptyList(); private static final CustomHighlighterRenderer RENDERER = (editor, highlighter, g) -> { int startOffset = highlighter.getStartOffset(); @@ -92,7 +89,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware final VisualPosition startPosition = editor.offsetToVisualPosition(off); int indentColumn = startPosition.column; - // It's considered that indent guide can cross not only white space but comments, javadocs etc. Hence, there is a possible + // It's considered that indent guide can cross not only white space but comments, javadoc etc. Hence, there is a possible // case that the first indent guide line is, say, single-line comment where comment symbols ('//') are located at the first // visual column. We need to calculate correct indent guide column then. int lineShift = 1; @@ -139,7 +136,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware } final EditorColorsScheme scheme = editor.getColorsScheme(); - g.setColor(selected ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR) : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR)); + g.setColor(scheme.getColor(selected ? EditorColors.SELECTED_INDENT_GUIDE_COLOR : EditorColors.INDENT_GUIDE_COLOR)); // There is a possible case that indent line intersects soft wrap-introduced text. Example: // this is a long line @@ -191,10 +188,8 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware } } }; - private volatile List myRanges; - private volatile List myDescriptors; - public IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { super(project, editor.getDocument(), false); myEditor = (EditorEx)editor; myFile = file; @@ -216,7 +211,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware ranges.add(new TextRange(myDocument.getLineStartOffset(descriptor.startLine), endOffset)); } - Collections.sort(ranges, RANGE_COMPARATOR); + Collections.sort(ranges, Segment.BY_START_OFFSET_THEN_END_OFFSET); myRanges = ranges; } @@ -350,11 +345,9 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware } private class IndentsCalculator { - - @NotNull public final Map myComments = ContainerUtilRt.newHashMap(); - - @NotNull final int[] lineIndents; // negative value means the line is empty (or contains a comment) and indent - // (denoted by absolute value) was deduced from enclosing non-empty lines + @NotNull final Map myComments = ContainerUtilRt.newHashMap(); + @NotNull final int[] lineIndents; // negative value means the line is empty (or contains a comment) and indent + // (denoted by absolute value) was deduced from enclosing non-empty lines @NotNull final CharSequence myChars; IndentsCalculator() { @@ -391,12 +384,9 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware } offset++; } - if (offset == lineEnd || isComment(offset)) { // treating commented lines in the same way as empty lines - lineIndents[line] = -1; // Blank line marker - } - else { - lineIndents[line] = column; - } + // treating commented lines in the same way as empty lines + // Blank line marker + lineIndents[line] = offset == lineEnd || isComment(offset) ? -1 : column; } int topIndent = 0; diff --git a/platform/util/src/com/intellij/openapi/util/Segment.java b/platform/util/src/com/intellij/openapi/util/Segment.java index a685d3a1191c..3470413a9535 100644 --- a/platform/util/src/com/intellij/openapi/util/Segment.java +++ b/platform/util/src/com/intellij/openapi/util/Segment.java @@ -15,6 +15,8 @@ */ package com.intellij.openapi.util; +import java.util.Comparator; + /** * User: cdr */ @@ -22,4 +24,13 @@ public interface Segment { Segment[] EMPTY_ARRAY = new Segment[0]; int getStartOffset(); int getEndOffset(); + + Comparator BY_START_OFFSET_THEN_END_OFFSET = new Comparator() { + @Override + public int compare(Segment r1, Segment r2) { + int result = r1.getStartOffset() - r2.getStartOffset(); + if (result == 0) result = r1.getEndOffset() - r2.getEndOffset(); + return result; + } + }; } diff --git a/plugins/devkit/src/actions/ToggleHighlightingMarkupAction.java b/plugins/devkit/src/actions/ToggleHighlightingMarkupAction.java index 9d1090f32f7b..457d5d4d7d56 100644 --- a/plugins/devkit/src/actions/ToggleHighlightingMarkupAction.java +++ b/plugins/devkit/src/actions/ToggleHighlightingMarkupAction.java @@ -17,26 +17,24 @@ package org.jetbrains.idea.devkit.actions; import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx; import com.intellij.codeInsight.daemon.impl.HighlightInfo; -import com.intellij.codeInsight.daemon.impl.IndentsPass; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.CommandProcessorEx; import com.intellij.openapi.command.UndoConfirmationPolicy; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -57,7 +55,7 @@ public class ToggleHighlightingMarkupAction extends AnAction { PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); if (editor == null || file == null) return; final Project project = file.getProject(); - CommandProcessorEx commandProcessor = (CommandProcessorEx)CommandProcessorEx.getInstance(); + CommandProcessorEx commandProcessor = (CommandProcessorEx)CommandProcessor.getInstance(); Object commandToken = commandProcessor.startCommand(project, e.getPresentation().getText(), e.getPresentation().getText(), UndoConfirmationPolicy.DEFAULT); try { WriteAction.run(() -> { @@ -101,7 +99,7 @@ public class ToggleHighlightingMarkupAction extends AnAction { pos = Math.max(matcher.end(1), matcher.end(2)); } while (matcher.find(pos)); - Collections.sort(ranges, IndentsPass.RANGE_COMPARATOR); + Collections.sort(ranges, Segment.BY_START_OFFSET_THEN_END_OFFSET); } if (!ranges.isEmpty()) { int pos = 0; @@ -112,7 +110,7 @@ public class ToggleHighlightingMarkupAction extends AnAction { sb.append(sequence, pos, sequence.length()); } else { - final int[] offset = new int[] {0}; + final int[] offset = {0}; final ArrayList infos = new ArrayList<>(); DaemonCodeAnalyzerEx.processHighlights( document, project, HighlightSeverity.WARNING, 0, sequence.length(), @@ -120,11 +118,11 @@ public class ToggleHighlightingMarkupAction extends AnAction { if (info.getSeverity() != HighlightSeverity.WARNING && info.getSeverity() != HighlightSeverity.ERROR) return true; if (info.getStartOffset() >= endOffset) return false; if (info.getEndOffset() > startOffset) { - offset[0] = appendInfo(info, sb, sequence, offset[0], infos, false); + offset[0] = appendInfo(info, sb, sequence, offset[0], infos); } return true; }); - offset[0] = appendInfo(null, sb, sequence, offset[0], infos, false); + offset[0] = appendInfo(null, sb, sequence, offset[0], infos); sb.append(sequence.subSequence(offset[0], sequence.length())); } document.setText(sb); @@ -134,27 +132,27 @@ public class ToggleHighlightingMarkupAction extends AnAction { StringBuilder sb, CharSequence sequence, int offset, - ArrayList infos, final boolean compact) { + ArrayList infos) { if (info == null || !infos.isEmpty() && getMaxEnd(infos) < info.getStartOffset()) { if (infos.size() == 1) { HighlightInfo cur = infos.remove(0); sb.append(sequence.subSequence(offset, cur.getStartOffset())); - appendTag(sb, cur, true, compact); + appendTag(sb, cur, true); sb.append(sequence.subSequence(cur.getStartOffset(), cur.getEndOffset())); - appendTag(sb, cur, false, compact); + appendTag(sb, cur, false); offset = cur.getEndOffset(); } else { // process overlapped LinkedList stack = new LinkedList<>(); for (HighlightInfo cur : infos) { - offset = processStack(stack, sb, sequence, offset, cur.getStartOffset(), compact); + offset = processStack(stack, sb, sequence, offset, cur.getStartOffset()); sb.append(sequence.subSequence(offset, cur.getStartOffset())); offset = cur.getStartOffset(); - appendTag(sb, cur, true, compact); + appendTag(sb, cur, true); stack.addLast(cur); } - offset = processStack(stack, sb, sequence, offset, sequence.length(), compact); + offset = processStack(stack, sb, sequence, offset, sequence.length()); infos.clear(); } } @@ -185,24 +183,20 @@ public class ToggleHighlightingMarkupAction extends AnAction { StringBuilder sb, CharSequence sequence, int offset, - final int endOffset, - final boolean compact) { + final int endOffset) { if (stack.isEmpty()) return offset; for (HighlightInfo cur = stack.peekLast(); cur != null && cur.getEndOffset() <= endOffset; cur = stack.peekLast()) { stack.removeLast(); if (offset <= cur.getEndOffset()) { sb.append(sequence.subSequence(offset, cur.getEndOffset())); } - else { - //System.out.println("Incorrect overlapping infos: " + offset + " > " + cur.getEndOffset()); - } offset = cur.getEndOffset(); - appendTag(sb, cur, false, compact); + appendTag(sb, cur, false); } return offset; } - private static void appendTag(StringBuilder sb, HighlightInfo cur, boolean opening, final boolean compact) { + private static void appendTag(StringBuilder sb, HighlightInfo cur, boolean opening) { sb.append("<"); if (!opening) sb.append("/"); if (cur.isAfterEndOfLine()) { @@ -211,7 +205,7 @@ public class ToggleHighlightingMarkupAction extends AnAction { else { sb.append(cur.getSeverity() == HighlightSeverity.WARNING ? "warning" : "error"); } - if (opening && !compact) { + if (opening) { sb.append(" descr=\"").append(cur.getDescription()).append("\""); }