From 8e26da6d48983929c59cbca71650b23c8a14a7f2 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Thu, 30 Apr 2026 15:40:44 +0800 Subject: [PATCH] fix: resolve null pointer crash when TextCalculator exits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Changed m_dataModel and m_calculator member variables from raw pointers to QPointer and QPointer to automatically track object destruction 2. Added onDataModelDestroyed() slot to handle model destruction: clear m_dataModel pointer, emit dataModelChanged() signal, and reset calculated widths to prevent stale pointer access 3. Extracted width reset logic into resetCalculatedWidths() method to avoid code duplication and ensure consistent state cleanup 4. In scheduleCalculation(), call resetCalculatedWidths() when model is null or disabled to properly reset cached values 5. In setEnabled(false), replaced inline width reset with resetCalculatedWidths() call for consistency 6. In destructor, always call disconnectDataModelSignals() without null check, as QPointer ensures safe disconnection even if model is destroyed 7. In TextCalculatorAttached::setCalculator(), added guard for duplicate calculator assignment and proper disconnect of previous calculator signals to prevent double connections 8. Always call updateElidedText() even when calculator is null to ensure text is properly cleared 9. Added QPointer include in header for QPointer usage Log: Fixed null pointer crash during TextCalculator exit in dock task panel Influence: 1. Test dock task panel close and exit operations to verify no crashes occur 2. Verify task item text display updates correctly when tasks change 3. Test with rapid task creation/deletion scenarios to check memory safety 4. Verify elided text updates properly when calculator is reassigned 5. Test disabled state transitions to confirm width values are reset correctly fix: 修复 TextCalculator 退出时的野指针崩溃问题 1. 将 m_dataModel 和 m_calculator 成员变量从原始指针改为 QPointer 和 QPointer,自动追踪对象 销毁 2. 新增 onDataModelDestroyed() 槽函数处理模型销毁:清除 m_dataModel 指 针、发射 dataModelChanged() 信号并重置计算宽度,防止野指针访问 3. 将宽度重置逻辑提取为 resetCalculatedWidths() 方法,避免代码重复并确保 状态一致清理 4. 在 scheduleCalculation() 中,当模型为空或禁用时调用 resetCalculatedWidths() 正确重置缓存值 5. 在 setEnabled(false) 中,将内联宽度重置替换为 resetCalculatedWidths() 调用,保持一致性 6. 在析构函数中,始终调用 disconnectDataModelSignals() 而不进行空检查, 因为 QPointer 确保即使模型已销毁也能安全断开连接 7. 在 TextCalculatorAttached::setCalculator() 中,添加重复计算器赋值防 护,并正确断开前一个计算器的信号连接,防止重复连接 8. 即使计算器为空也始终调用 updateElidedText(),确保正确清理文本 9. 在头文件中添加 QPointer 包含以支持 QPointer 使用 Log: 修复 Dock 任务面板 TextCalculator 退出时的野指针崩溃问题 Influence: 1. 测试 dock 任务面板的关闭和退出操作,验证无崩溃发生 2. 验证任务项文本显示在任务变更时正确更新 3. 测试快速创建/删除任务场景,检查内存安全性 4. 验证当计算器重新赋值时省略文本正确更新 5. 测试禁用状态切换,确认宽度值正确重置 --- panels/dock/taskmanager/textcalculator.cpp | 65 ++++++++++++++++------ panels/dock/taskmanager/textcalculator.h | 9 ++- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/panels/dock/taskmanager/textcalculator.cpp b/panels/dock/taskmanager/textcalculator.cpp index 0151311e9..b3f5c6e4a 100644 --- a/panels/dock/taskmanager/textcalculator.cpp +++ b/panels/dock/taskmanager/textcalculator.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025-2026 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -30,9 +30,7 @@ TextCalculator::TextCalculator(QObject *parent) TextCalculator::~TextCalculator() { - if (m_dataModel) { - disconnectDataModelSignals(); - } + disconnectDataModelSignals(); } void TextCalculator::setFont(const QFont &font) @@ -113,10 +111,7 @@ void TextCalculator::setEnabled(bool enabled) if (m_enabled) { scheduleCalculation(); } else { - m_optimalSingleTextWidth = 0.0; - m_totalWidth = 0; - emit optimalSingleTextWidthChanged(); - emit totalWidthChanged(); + resetCalculatedWidths(); } emit enabledChanged(); } @@ -137,11 +132,12 @@ void TextCalculator::connectDataModelSignals() this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles) { const auto titleRole = m_dataModel->roleNames().key("title"); - if (roles.contains(titleRole) || roles.isEmpty()) { - scheduleCalculation(); - } - }); + if (roles.contains(titleRole) || roles.isEmpty()) { + scheduleCalculation(); + } + }); connect(m_dataModel, &QAbstractItemModel::modelReset, this, &TextCalculator::onDataModelChanged); + connect(m_dataModel, &QObject::destroyed, this, &TextCalculator::onDataModelDestroyed); } } @@ -157,17 +153,41 @@ void TextCalculator::onDataModelChanged() scheduleCalculation(); } +void TextCalculator::onDataModelDestroyed() +{ + m_dataModel = nullptr; + emit dataModelChanged(); + resetCalculatedWidths(); +} + void TextCalculator::scheduleCalculation() { if (!complete) return; if (!m_enabled || !m_dataModel) { + resetCalculatedWidths(); return; } calculateOptimalTextWidth(); } +void TextCalculator::resetCalculatedWidths() +{ + const bool optimalWidthWasSet = !qFuzzyIsNull(m_optimalSingleTextWidth); + const bool totalWidthWasSet = !qFuzzyIsNull(m_totalWidth); + + m_optimalSingleTextWidth = 0.0; + m_totalWidth = 0.0; + + if (optimalWidthWasSet) { + emit optimalSingleTextWidthChanged(); + } + if (totalWidthWasSet) { + emit totalWidthChanged(); + } +} + qreal TextCalculator::calculateBaselineWidth(int charCount) const { if (m_baselineWidthCache.contains(charCount)) { @@ -372,11 +392,22 @@ QString TextCalculatorAttached::elidedText() const void TextCalculatorAttached::setCalculator(TextCalculator *calculator) { - if (calculator) { - m_calculator = calculator; - connect(calculator, &TextCalculator::optimalSingleTextWidthChanged, this, &TextCalculatorAttached::updateElidedText); - updateElidedText(); + if (m_calculator == calculator) { + return; + } + + if (m_calculator) { + disconnect(m_calculator, nullptr, this, nullptr); + } + + m_calculator = calculator; + emit calculatorChanged(); + + if (m_calculator) { + connect(m_calculator, &TextCalculator::optimalSingleTextWidthChanged, this, &TextCalculatorAttached::updateElidedText); } + + updateElidedText(); } TextCalculator *TextCalculatorAttached::calculator() @@ -422,7 +453,7 @@ void TextCalculatorAttached::updateElidedText() if (visibleText.isEmpty()) { visibleText = {}; } - + if (visibleText != m_text) { m_ellipsisWidth = fontMetrics.horizontalAdvance(QString::fromUtf8("…")); emit ellipsisWidthChanged(); diff --git a/panels/dock/taskmanager/textcalculator.h b/panels/dock/taskmanager/textcalculator.h index 58a9e0323..6152b3b1d 100644 --- a/panels/dock/taskmanager/textcalculator.h +++ b/panels/dock/taskmanager/textcalculator.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025-2026 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -6,6 +6,7 @@ #include #include +#include #include namespace dock @@ -55,7 +56,7 @@ private Q_SLOTS: QString m_text; QString m_elidedText; qreal m_ellipsisWidth = 0.0; - TextCalculator *m_calculator; + QPointer m_calculator; bool m_initialized = false; }; @@ -159,12 +160,14 @@ class TextCalculator : public QObject, public QQmlParserStatus private slots: void onDataModelChanged(); + void onDataModelDestroyed(); void calculateOptimalTextWidth(); private: void connectDataModelSignals(); void disconnectDataModelSignals(); void scheduleCalculation(); + void resetCalculatedWidths(); qreal calculateBaselineWidth(int charCount) const; qreal calculateElidedTextWidth(const QString &text, qreal maxWidth) const; @@ -178,7 +181,7 @@ private slots: qreal m_cellSize; int m_spacing; int m_itemPadding; - QAbstractItemModel *m_dataModel; + QPointer m_dataModel; qreal m_remainingSpace; bool m_enabled;