From 9f18ec44a44e53228f17b08d719d88d062861ea6 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Fri, 15 Jul 2022 20:33:52 +0200 Subject: [PATCH 1/5] check block safety --- spyder/plugins/editor/panels/scrollflag.py | 7 ++++--- spyder/plugins/editor/utils/editor.py | 17 +++++++++++++++++ spyder/plugins/editor/widgets/codeeditor.py | 12 ++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/editor/panels/scrollflag.py b/spyder/plugins/editor/panels/scrollflag.py index 5a761f24935..2310b576859 100644 --- a/spyder/plugins/editor/panels/scrollflag.py +++ b/spyder/plugins/editor/panels/scrollflag.py @@ -20,6 +20,7 @@ # Local imports from spyder.api.panel import Panel from spyder.plugins.completion.api import DiagnosticSeverity +from spyder.plugins.editor.utils.editor import block_safe REFRESH_RATE = 1000 @@ -221,7 +222,7 @@ def paintEvent(self, event): if editor.verticalScrollBar().maximum() == 0: # No scroll for block in dict_flag_lists[flag_type]: - if not block.isValid(): + if not block_safe(block): continue geometry = editor.blockBoundingGeometry(block) rect_y = ceil( @@ -233,7 +234,7 @@ def paintEvent(self, event): elif last_line == 0: # Only one line for block in dict_flag_lists[flag_type]: - if not block.isValid(): + if not block_safe(block): continue rect_y = ceil(first_y_pos) painter.drawRect(rect_x, rect_y, rect_w, rect_h) @@ -243,7 +244,7 @@ def paintEvent(self, event): # If the file is too long, do not freeze the editor next_line = 0 for block in dict_flag_lists[flag_type]: - if not block.isValid(): + if not block_safe(block): continue block_line = block.firstLineNumber() # block_line = -1 if invalid diff --git a/spyder/plugins/editor/utils/editor.py b/spyder/plugins/editor/utils/editor.py index 1b6b127d54d..b1b83b35b33 100644 --- a/spyder/plugins/editor/utils/editor.py +++ b/spyder/plugins/editor/utils/editor.py @@ -51,6 +51,23 @@ def drift_color(base_color, factor=110): return base_color.lighter(factor + 10) +def block_safe(block): + """ + Check if the block is safe to work with. + + A BlockUserData must have been set on the block while it was known safe. + + If an editor is cleared by editor.clear() or editor.set_text() for example, + all the old blocks will continue to report block.isValid() == True + but will raise a Segmentation Fault on almost all methods. + + One way to check is that the userData is reset to None or + QTextBlockUserData. So if a block is known to have setUserData to + BlockUserData, this fact can be used to check the block. + """ + return block.isValid() and isinstance(block.userData(), BlockUserData) + + class BlockUserData(QTextBlockUserData): def __init__(self, editor, color=None, selection_start=None, selection_end=None): diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py index d18c33548ea..5c959d8e6d0 100644 --- a/spyder/plugins/editor/widgets/codeeditor.py +++ b/spyder/plugins/editor/widgets/codeeditor.py @@ -2579,7 +2579,11 @@ def __mark_occurrences(self): extra_selections = self.get_extra_selections('occurrences') first_occurrence = None while cursor: - self.occurrences.append(cursor.block()) + block = cursor.block() + if not block.userData(): + # Add user data to check block validity + block.setUserData(BlockUserData(self)) + self.occurrences.append(block) selection = self.get_selection(cursor) if len(selection.cursor.selectedText()) > 0: extra_selections.append(selection) @@ -2623,7 +2627,11 @@ def highlight_found_results(self, pattern, word=False, regexp=False, selection = TextDecoration(self.textCursor()) selection.format.setBackground(self.found_results_color) selection.cursor.setPosition(pos1) - self.found_results.append(selection.cursor.block()) + block = selection.cursor.block() + if not block.userData(): + # Add user data to check block validity + block.setUserData(BlockUserData(self)) + self.found_results.append(block) selection.cursor.setPosition(pos2, QTextCursor.KeepAnchor) extra_selections.append(selection) self.set_extra_selections('find', extra_selections) From c49763ceda615a7b31e7aead20e917c261ab1471 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 16 Jul 2022 02:08:10 +0200 Subject: [PATCH 2/5] pep8 --- spyder/plugins/editor/utils/editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder/plugins/editor/utils/editor.py b/spyder/plugins/editor/utils/editor.py index b1b83b35b33..afcf3296a74 100644 --- a/spyder/plugins/editor/utils/editor.py +++ b/spyder/plugins/editor/utils/editor.py @@ -61,8 +61,8 @@ def block_safe(block): all the old blocks will continue to report block.isValid() == True but will raise a Segmentation Fault on almost all methods. - One way to check is that the userData is reset to None or - QTextBlockUserData. So if a block is known to have setUserData to + One way to check is that the userData is reset to None or + QTextBlockUserData. So if a block is known to have setUserData to BlockUserData, this fact can be used to check the block. """ return block.isValid() and isinstance(block.userData(), BlockUserData) From 204bc696451f8e0f653714fd0ad750fba1745e5e Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 20 Jul 2022 19:01:00 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Carlos Cordoba --- spyder/plugins/editor/utils/editor.py | 11 ++++++----- spyder/plugins/editor/widgets/codeeditor.py | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/editor/utils/editor.py b/spyder/plugins/editor/utils/editor.py index afcf3296a74..bddaa85874d 100644 --- a/spyder/plugins/editor/utils/editor.py +++ b/spyder/plugins/editor/utils/editor.py @@ -55,14 +55,15 @@ def block_safe(block): """ Check if the block is safe to work with. - A BlockUserData must have been set on the block while it was known safe. + A BlockUserData must have been set on the block while it was known to be + safe. If an editor is cleared by editor.clear() or editor.set_text() for example, - all the old blocks will continue to report block.isValid() == True - but will raise a Segmentation Fault on almost all methods. + all old blocks will continue to report block.isValid() == True, but will + raise a segmentation fault on almost all methods. - One way to check is that the userData is reset to None or - QTextBlockUserData. So if a block is known to have setUserData to + One way to check if a block is valid is that the userData is reset to None + or QTextBlockUserData. So if a block is known to have setUserData to BlockUserData, this fact can be used to check the block. """ return block.isValid() and isinstance(block.userData(), BlockUserData) diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py index 5c959d8e6d0..73c3b23d486 100644 --- a/spyder/plugins/editor/widgets/codeeditor.py +++ b/spyder/plugins/editor/widgets/codeeditor.py @@ -2584,6 +2584,7 @@ def __mark_occurrences(self): # Add user data to check block validity block.setUserData(BlockUserData(self)) self.occurrences.append(block) + selection = self.get_selection(cursor) if len(selection.cursor.selectedText()) > 0: extra_selections.append(selection) @@ -2627,11 +2628,13 @@ def highlight_found_results(self, pattern, word=False, regexp=False, selection = TextDecoration(self.textCursor()) selection.format.setBackground(self.found_results_color) selection.cursor.setPosition(pos1) + block = selection.cursor.block() if not block.userData(): # Add user data to check block validity block.setUserData(BlockUserData(self)) self.found_results.append(block) + selection.cursor.setPosition(pos2, QTextCursor.KeepAnchor) extra_selections.append(selection) self.set_extra_selections('find', extra_selections) From 6f0fa053afd43b0731e9918c5db280e3c275a14e Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 20 Jul 2022 19:03:50 +0200 Subject: [PATCH 4/5] rename block_safe --- spyder/plugins/editor/panels/scrollflag.py | 8 ++++---- spyder/plugins/editor/utils/editor.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/editor/panels/scrollflag.py b/spyder/plugins/editor/panels/scrollflag.py index 2310b576859..1ae496f039d 100644 --- a/spyder/plugins/editor/panels/scrollflag.py +++ b/spyder/plugins/editor/panels/scrollflag.py @@ -20,7 +20,7 @@ # Local imports from spyder.api.panel import Panel from spyder.plugins.completion.api import DiagnosticSeverity -from spyder.plugins.editor.utils.editor import block_safe +from spyder.plugins.editor.utils.editor import is_block_safe REFRESH_RATE = 1000 @@ -222,7 +222,7 @@ def paintEvent(self, event): if editor.verticalScrollBar().maximum() == 0: # No scroll for block in dict_flag_lists[flag_type]: - if not block_safe(block): + if not is_block_safe(block): continue geometry = editor.blockBoundingGeometry(block) rect_y = ceil( @@ -234,7 +234,7 @@ def paintEvent(self, event): elif last_line == 0: # Only one line for block in dict_flag_lists[flag_type]: - if not block_safe(block): + if not is_block_safe(block): continue rect_y = ceil(first_y_pos) painter.drawRect(rect_x, rect_y, rect_w, rect_h) @@ -244,7 +244,7 @@ def paintEvent(self, event): # If the file is too long, do not freeze the editor next_line = 0 for block in dict_flag_lists[flag_type]: - if not block_safe(block): + if not is_block_safe(block): continue block_line = block.firstLineNumber() # block_line = -1 if invalid diff --git a/spyder/plugins/editor/utils/editor.py b/spyder/plugins/editor/utils/editor.py index bddaa85874d..a978490f025 100644 --- a/spyder/plugins/editor/utils/editor.py +++ b/spyder/plugins/editor/utils/editor.py @@ -51,7 +51,7 @@ def drift_color(base_color, factor=110): return base_color.lighter(factor + 10) -def block_safe(block): +def is_block_safe(block): """ Check if the block is safe to work with. From d59aed3a37585a2c471346a26ba0da6b48f2a206 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 20 Jul 2022 19:04:40 +0200 Subject: [PATCH 5/5] finish removing after https://github.com/spyder-ide/spyder/pull/16396 --- spyder/plugins/editor/utils/editor.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/spyder/plugins/editor/utils/editor.py b/spyder/plugins/editor/utils/editor.py index a978490f025..28096710399 100644 --- a/spyder/plugins/editor/utils/editor.py +++ b/spyder/plugins/editor/utils/editor.py @@ -85,15 +85,6 @@ def __init__(self, editor, color=None, selection_start=None, self.selection_start = selection_start self.selection_end = selection_end - # Add a reference to the user data in the editor as the block won't. - # The list should /not/ be used to list BlockUserData as the blocks - # they refer to might not exist anymore. - # This prevents a segmentation fault. - if editor is None: - # Won't be destroyed - self.refloop = self - return - def _selection(self): """ Function to compute the selection.