Skip to content

fix: eliminate remaining memory leaks in application-tray#457

Merged
yixinshark merged 1 commit into
linuxdeepin:masterfrom
yixinshark:fix/memory-leaks-359161
May 12, 2026
Merged

fix: eliminate remaining memory leaks in application-tray#457
yixinshark merged 1 commit into
linuxdeepin:masterfrom
yixinshark:fix/memory-leaks-359161

Conversation

@yixinshark
Copy link
Copy Markdown
Contributor

@yixinshark yixinshark commented May 11, 2026

SNI icon handling:

  • Add m_cachedAttentionIcon / m_cachedOverlayIcon to cache attention
    and overlay icons, following the same pattern as m_cachedIcon, to
    avoid repeated D-Bus pixmap reads during attention state blinking
  • Skip icon pixmap updates on NewIcon / NewOverlayIcon /
    NewAttentionIcon signals when the client provides a theme icon name,
    since the getter already resolves icons from the icon theme in that
    case and the cached pixmap is not needed
  • Rework dbusImageList2QIcon: deep-copy the DBusImageList before
    modifying pixel data to fix const-cast undefined behavior with Qt6
    QByteArray; use quint32* for correct type semantics and bounded
    iteration (size/4 instead of raw byte stride)

XCB image lifecycle (XEmbed path):

  • Fix xcb_image_t leak in Util::convertFromNative when the pixel depth
    falls into the unsupported default case
  • Explicitly call xcb_image_destroy when QImage construction from
    xcb_image_t fails, since Qt's cleanup callback is not invoked in the
    null state
  • Replace QSharedPointer/QScopedPointer default delete deleters with
    explicit free() for xcb reply pointers allocated via malloc, fixing
    undefined behavior and potential leaks in both Util and
    XembedProtocolHandler
  • Refactor getX11WindowImageNonComposite to deep-copy the image data
    via QImage::copy and immediately destroy the xcb_image_t, bypassing
    Qt's unreliable cleanup callback that can be evaded by std::move and
    heuristic mask operations

Build:

  • Fix debian/rules to export QT_SELECT=qt6 instead of qt5 since
    the tray plugin links against Qt6

Together with the corresponding DTK fix (QMetaType::destruct before
construct in DDBusExtendedAbstractInterface::internalPropGet), this
reduces application-tray RSS growth from ~23 KB/s to negligible levels.

SNI 图标处理:

  • 新增 m_cachedAttentionIcon / m_cachedOverlayIcon 缓存 attention/overlay
    图标,沿用 m_cachedIcon 的缓存模式,避免闪烁状态期间每次 paint
    重复走 DBus 属性读取
  • NewIcon / NewOverlayIcon / NewAttentionIcon 信号处理中,当客户端提供
    主题图标名时跳过 pixmap 更新,因为 getter 已经优先从图标主题加载
  • 重写 dbusImageList2QIcon: 修改像素数据前显式深拷贝 DBusImageList,
    修正 Qt6 QByteArray 的 const_cast 未定义行为;使用 quint32* 替代
    qint32* 获得正确的类型语义,循环边界改为 size/4

XCB 图像生命周期:

  • convertFromNative 函数 default 分支中释放 xcb_image_t 防止泄漏
  • QImage 从 xcb_image_t 构造失败时显式调用 xcb_image_destroy,弥补
    Qt 在 null state 下不触发 cleanup callback 的问题
  • QSharedPointer/QScopedPointer 使用 free 替代默认的 delete 释放
    malloc 分配的 xcb reply 指针,修正未定义行为
  • getX11WindowImageNonComposite 通过 QImage::copy 深拷贝后直接
    xcb_image_destroy,绕过 Qt cleanup callback 被 std::move 和
    heuristic mask 绕过的风险

构建:

  • debian/rules 修正 QT_SELECT=qt6,匹配实际 Qt6 链接

配合 DTK 修复(DDBusExtendedAbstractInterface::internalPropGet 中
construct 前添加 destruct),application-tray RSS 增长从约 23 KB/s
降至可忽略水平。

Log: fix: eliminate remaining memory leaks in application-tray
Pms: BUG-359161

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @yixinshark, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

SNI icon handling:
- Add m_cachedAttentionIcon / m_cachedOverlayIcon to cache attention
  and overlay icons, following the same pattern as m_cachedIcon, to
  avoid repeated D-Bus pixmap reads during attention state blinking
- Skip icon pixmap updates on NewIcon / NewOverlayIcon /
  NewAttentionIcon signals when the client provides a theme icon name,
  since the getter already resolves icons from the icon theme in that
  case and the cached pixmap is not needed
- Rework dbusImageList2QIcon: deep-copy the DBusImageList before
  modifying pixel data to fix const-cast undefined behavior with Qt6
  QByteArray; use quint32* for correct type semantics and bounded
  iteration (size/4 instead of raw byte stride)

XCB image lifecycle (XEmbed path):
- Fix xcb_image_t leak in Util::convertFromNative when the pixel depth
  falls into the unsupported default case
- Explicitly call xcb_image_destroy when QImage construction from
  xcb_image_t fails, since Qt's cleanup callback is not invoked in the
  null state
- Replace QSharedPointer/QScopedPointer default delete deleters with
  explicit free() for xcb reply pointers allocated via malloc, fixing
  undefined behavior and potential leaks in both Util and
  XembedProtocolHandler
- Refactor getX11WindowImageNonComposite to deep-copy the image data
  via QImage::copy and immediately destroy the xcb_image_t, bypassing
  Qt's unreliable cleanup callback that can be evaded by std::move and
  heuristic mask operations

Build:
- Fix debian/rules to export QT_SELECT=qt6 instead of qt5 since
  the tray plugin links against Qt6

Together with the corresponding DTK fix (QMetaType::destruct before
construct in DDBusExtendedAbstractInterface::internalPropGet), this
reduces application-tray RSS growth from ~23 KB/s to negligible levels.

SNI 图标处理:
- 新增 m_cachedAttentionIcon / m_cachedOverlayIcon 缓存 attention/overlay
  图标,沿用 m_cachedIcon 的缓存模式,避免闪烁状态期间每次 paint
  重复走 DBus 属性读取
- NewIcon / NewOverlayIcon / NewAttentionIcon 信号处理中,当客户端提供
  主题图标名时跳过 pixmap 更新,因为 getter 已经优先从图标主题加载
- 重写 dbusImageList2QIcon: 修改像素数据前显式深拷贝 DBusImageList,
  修正 Qt6 QByteArray 的 const_cast 未定义行为;使用 quint32* 替代
  qint32* 获得正确的类型语义,循环边界改为 size/4

XCB 图像生命周期:
- convertFromNative 函数 default 分支中释放 xcb_image_t 防止泄漏
- QImage 从 xcb_image_t 构造失败时显式调用 xcb_image_destroy,弥补
  Qt 在 null state 下不触发 cleanup callback 的问题
- QSharedPointer/QScopedPointer 使用 free 替代默认的 delete 释放
  malloc 分配的 xcb reply 指针,修正未定义行为
- getX11WindowImageNonComposite 通过 QImage::copy 深拷贝后直接
  xcb_image_destroy,绕过 Qt cleanup callback 被 std::move 和
  heuristic mask 绕过的风险

构建:
- debian/rules 修正 QT_SELECT=qt6,匹配实际 Qt6 链接

配合 DTK 修复(DDBusExtendedAbstractInterface::internalPropGet 中
construct 前添加 destruct),application-tray RSS 增长从约 23 KB/s
降至可忽略水平。

Log: fix: eliminate remaining memory leaks in application-tray
Pms: BUG-359161
@yixinshark yixinshark force-pushed the fix/memory-leaks-359161 branch from c3c668b to e757a44 Compare May 11, 2026 06:28
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

你好!我是你的智能编程助手 CodeGeeX。我已仔细审查了你提供的 Git Diff。

本次代码变更主要涉及将 Qt 版本从 Qt5 升级到 Qt6,对 SNI(StatusNotifierItem)托盘图标的缓存机制进行了优化,并修复了多处 X11/XCB 相关的严重内存泄漏和潜在的内存安全问题。

总体来说,这是一次质量很高的重构。以下我将从语法逻辑、代码质量、代码性能和代码安全四个维度为你提供详细的审查意见和改进建议:

一、 语法与逻辑

  1. SNI 图标缓存逻辑不一致

    • init() 函数中,你初始化缓存时没有判断 iconName 是否为空:
      m_cachedIcon = dbusImageList2QIcon(m_sniInter->iconPixmap());
      m_cachedAttentionIcon = dbusImageList2QIcon(m_sniInter->attentionIconPixmap());
      m_cachedOverlayIcon = dbusImageList2QIcon(m_sniInter->overlayIconPixmap());
    • 但在信号处理的 Lambda 中,你增加了 if (m_sniInter->iconName().isEmpty()) 的判断。
    • 问题:如果初始状态下 iconName 不为空,init() 依然会去解析 pixmap 浪费性能;且如果后续信号触发时 iconName 不为空,缓存将不会被更新,导致逻辑不一致。
    • 建议:在 init() 中也加上 isEmpty() 判断,保持逻辑统一:
      if (m_sniInter->iconName().isEmpty())
          m_cachedIcon = dbusImageList2QIcon(m_sniInter->iconPixmap());
      // attentionIcon 和 overlayIcon 同理
  2. dbusImageList2QIcon 中的字节序转换越界风险

    • 原代码使用 *(qint32 *) 强转,修改后使用了 reinterpret_cast<quint32 *>,这是很好的改进。但是循环条件 i < image.pixels.size() / 4 存在逻辑漏洞。
    • 问题:如果 image.pixels.size() 不能被 4 整除(例如由于传输错误导致数据截断),size() / 4 会向下取整,导致末尾的 1-3 个字节未被转换,但后续 QImage 仍会按 4 字节 ARGB 解析,可能导致颜色偏差。
    • 建议:增加对数据大小的校验,确保数据完整:
      if (image.pixels.size() % 4 != 0) continue; // 或打印警告
      quint32 *pixels = reinterpret_cast<quint32 *>(image.pixels.data());
      for (int i = 0; i < image.pixels.size() / 4; i++)
          pixels[i] = qFromBigEndian(pixels[i]);

二、 代码质量

  1. QSharedPointer 的删除器冗余

    • 你为 XCB 的 reply 指针添加了自定义删除器 [](xcb_get_geometry_reply_t* ptr){ free(ptr); },这修复了原先默认调用 delete 导致的未定义行为,非常棒!
    • 建议:XCB 的 reply 都是通过 C 库中的 malloc 分配的,必须用 free 释放。既然代码中多处用到,建议封装一个通用的工具函数或模板,提高代码复用率和可读性:
      template<typename T>
      auto makeXcbReply(T* ptr) {
          return QSharedPointer<T>(ptr, [](T* p) { free(p); });
      }
      // 使用时:
      auto clientGeom = makeXcbReply(xcb_get_geometry_reply(c, cookieSize, nullptr));
  2. Qt6 升级与 QImage 深拷贝

    • 在 Qt6 中,QImage 的隐式共享(写时复制)机制更加严格,且 QImage::Format 枚举在 Qt6 中有些微调。你在 getX11WindowImageNonCompositeconvertFromNative 中大量使用了 QImage::copy() 进行深拷贝。
    • 评价:这是非常正确且必要的做法。因为 XCB 的 image 数据生命周期由 xcb_image_destroy 控制,必须在使用前将数据深拷贝到 Qt 管理的内存中。原先依赖 QImage::cleanupFunction 的做法在复杂的异常控制流中极易出错,你的重构让内存所有权更加清晰。

三、 代码性能

  1. dbusImageList2QIcon 中的深拷贝开销

    • 你增加了一行 DBusImageList copy = dbusImageList;
    • 问题DBusImageList 是一个包含 QVector<DBusImage> 的结构体,而 DBusImage 内部包含 QByteArray。这意味着你在每次转换图标时,都对整个图像列表进行了深拷贝(仅仅是为了做字节序转换)。
    • 建议:如果 dbusImageList 是 const 引用传入,且你确认该函数外部不再需要原始数据,可以直接去掉 const 修饰在原数据上修改;如果必须保留原数据,目前的深拷贝是必须的。但可以考虑仅对需要转换的像素数据区域进行操作,或者确认调用方是否可以传入非 const 引用以节省一次大量的内存分配。
  2. SNI 图标获取的 D-Bus 调用阻塞

    • 原先的 overlayIcon() 等函数是直接调用 m_sniInter->overlayIconPixmap(),这会引发一次同步的 D-Bus 调用。
    • 评价:你将它们改为了读取缓存 m_cachedOverlayIcon,这是一个极大的性能提升!避免了在 UI 渲染线程(主线程)中进行阻塞的 D-Bus 通信,有效防止了界面卡顿。

四、 代码安全

  1. XCB 响应的空指针解引用风险

    • getX11WindowGeometry 中:
      auto clientGeom = QSharedPointer<...>(xcb_get_geometry_reply(...));
      return clientGeom ? QRect(clientGeom->x, ...) : QRect();
    • 评价:很好,你做了空指针检查。
    • 注意:请确保所有 xcb_*_reply 的调用处都做了类似的空指针检查。例如 xembedprotocolhandler.cpp 中的 sendClick 也做了检查,这是安全的。
  2. 严格别名规则 修复

    • 原代码 *(qint32 *)(image_data + i) = qFromBigEndian(*(qint32 *)(image_data + i)); 违反了 C++ 的严格别名规则,在某些编译器优化下会产生未定义行为。
    • 评价:修改为 reinterpret_cast<quint32*>(image.pixels.data()) 配合数组下标访问,虽然本质上仍然是类型双关,但通过 quint32 指针操作是标准中更被广泛接受的实现方式,且 qFromBigEndian 本身也处理了底层转换,安全性得到了提升。
  3. 资源泄漏修复

    • 修复了 xcb_get_geometry_replyxcb_get_window_attributes_reply_txcb_query_pointer_reply_t 的内存泄漏。原先使用 QSharedPointerQScopedPointer 但不提供 free 删除器,会导致 C 分配的内存被 C++ 的 delete 释放,这是严重的堆损坏问题。你的修复非常关键。

总结

本次提交质量很高,解决了从 Qt5 迁移到 Qt6 过程中的核心内存管理问题,特别是 XCB 层面的内存泄漏和 QImage 数据悬挂指针问题。SNI 的缓存优化也显著改善了性能。

建议修改点

  1. 统一 init() 与信号槽中对 iconName.isEmpty() 的判断逻辑。
  2. 检查 image.pixels.size() % 4 != 0 的边界情况。
  3. 考虑提取 XCB Reply 的 QSharedPointer 创建逻辑为工具函数,提升代码整洁度。

@yixinshark yixinshark changed the title Fix/memory leaks 359161 fix: eliminate remaining memory leaks in application-tray May 11, 2026
@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: robertkill, yixinshark

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Comment thread plugins/application-tray/util.cpp
@yixinshark yixinshark merged commit 4356190 into linuxdeepin:master May 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants