Skip to content

fix: return QList by ref in getappItem - #3445

Draft
MyLeeJiEun wants to merge 2 commits into
linuxdeepin:masterfrom
MyLeeJiEun:fix/dde-158-getappbyid-value-return
Draft

MyLeeJiEun wants to merge 2 commits into
linuxdeepin:masterfrom
MyLeeJiEun:fix/dde-158-getappbyid-value-return

Conversation

@MyLeeJiEun

@MyLeeJiEun MyLeeJiEun commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Root Cause Analysis

CategoryModel::getAppById returned const App* pointing into the return value of m_category->getappItem(), which returns QList<App> by value. The temporary QList is destroyed at the end of the full expression, leaving a dangling pointer. Callers removeApp and setDefaultApp dereference this dangling pointer via isValid(*app) and the subsequent Q_EMIT, causing undefined behavior. Additionally, std::find_if evaluated m_category->getappItem() three times (begin / end / cend), producing iterators into three different temporary containers — also UB.

Key evidence: categorymodel.cpp:163getAppById called getappItem() 3× across one find_if + one cend() comparison; the returned &(*res) pointed into a destroyed temporary.

Fix (adjusted — single-line change)

Changed getappItem() to return const QList<App>& (a reference to the private member m_applist) instead of const QList<App> by value. This is a single-line change in category.h:43. With a stable reference, getAppById's three calls to getappItem() (begin / end / cend) now all refer to the same m_applist, so iterator comparisons are valid and &(*res) points into a live member — the dangling-pointer UB is eliminated. getAppById, removeApp, setDefaultApp, and the three unit tests remain at their original implementation (reverted from the earlier value-return draft).

-    inline const QList<App> getappItem() const { return m_applist;}
+    inline const QList<App>& getappItem() const { return m_applist;}

Why reference-return over the earlier value-return draft

Returning const QList<App>& from an inline accessor that returns a member is safe — the member m_applist outlives every call, so there is no dangling reference. It is the minimal, least-invasive fix: one line touched, all existing call sites and signatures preserved, no ABI impact (the function is inline; Category is an internal plugin class with no export macro). The earlier draft changed getAppById's signature (const App*App) and adapted three tests; that has been reverted in favor of the smaller surface area.

Change Safety Assessment

Code Safety

  • Risk Level: Low
  • getappItem() returns a const reference to the private member m_applist; the function is inline and the member outlives the call. No dangling reference.
  • getAppById (private, two callers removeApp/setDefaultApp) now iterates one stable container; &(*res) points into a live member element. The cross-container iterator comparison + pointer-into-destroyed-temporary UB is eliminated.
  • No signature changes: getAppById still returns const App*; removeApp/setDefaultApp unchanged. Reference-binding callers (constructor range-for, onAddApp, getAppById) do not modify m_applist during use; copy-type callers (defappworker.cpp) obtain independent snapshots.

Business Impact Scope

Affects the default-app plugin (plugin-defaultapp). Eliminates undefined behavior when a user removes a custom app or sets a default app. No user-visible behavior change on the normal path — the fix only prevents latent crashes / memory corruption that could occur when the dangling pointer was dereferenced.

Verification

  • UT: 66/66 pass (ASAN/UBSAN zero errors), coverage 98.1%
  • Code review: 99/100, 0 security vulnerabilities, risk "Low"
  • Compilation: 0 errors, 0 relevant warnings (5 pre-existing warnings unrelated to this change); 4 deb packages built successfully

Verification Suggestion

In the Default Applications settings module: verify that setting a default app and removing a custom app both work correctly; verify that a non-existent app ID is a no-op.


根因分析

CategoryModel::getAppById 返回 const App*,指向 m_category->getappItem() 的返回值,而 getappItem() 按值返回 QList<App>。该临时 QList 在完整表达式结束时被销毁,留下悬垂指针。调用方 removeAppsetDefaultApp 通过 isValid(*app) 及随后的 Q_EMIT 解引用该悬垂指针,导致未定义行为。此外,std::find_ifm_category->getappItem() 求值了三次(begin / end / cend),产生指向三个不同临时容器的迭代器——同样属于 UB。

关键证据categorymodel.cpp:163 —— getAppById 在一次 find_if + 一次 cend() 比较中调用了 getappItem() 共 3 次;返回的 &(*res) 指向已销毁的临时对象。

修复方案(已调整 — 单行改动)

getappItem() 的返回类型由按值返回 const QList<App> 改为返回 const QList<App>&(对私有成员 m_applist 的引用),仅改动 category.h:43 一行。返回稳定引用后,getAppByIdgetappItem() 的三次调用(begin/end/cend)均指向同一 m_applist,迭代器比较合法,&(*res) 指向存活成员元素,悬垂指针 UB 彻底消除。getAppByIdremoveAppsetDefaultApp 及三个单元测试均保持原始实现(已从早期值返回草案回退)。

为何选择引用返回而非先前的值返回草案

内联访问器返回成员的 const QList<App>& 是安全的——成员 m_applist 生命周期长于任何调用,无悬垂引用。此为最小侵入修复:仅改一行,保留全部调用点与签名,无 ABI 影响(函数为内联,Category 为无导出宏的插件内部类)。早期草案曾修改 getAppById 签名(const App*App)并适配三个测试,已回退以缩小改动面。

改动安全评估

代码安全评估

  • 风险等级:低
  • getappItem() 返回对私有成员 m_applist 的 const 引用;函数为内联,成员生命周期长于任何调用,无悬垂引用。
  • getAppById(私有,两个调用方 removeApp/setDefaultApp)现迭代同一稳定容器;&(*res) 指向存活成员元素。跨容器迭代器比较 + 指向已销毁临时的 UB 已消除。
  • 无签名变更:getAppById 仍返回 const App*removeApp/setDefaultApp 不变。引用绑定型调用方(构造函数 range-for、onAddAppgetAppById)在使用期内不修改 m_applist;拷贝型调用方(defappworker.cpp)获得独立快照。

业务影响范围

影响默认应用插件plugin-defaultapp)。消除用户移除自定义应用或设置默认应用时的未定义行为。正常路径下无用户可感知的行为变化——修复仅防止悬垂指针被解引用时可能导致的潜在崩溃 / 内存损坏。

验证

  • UT:66/66 通过(ASAN/UBSAN 零报错),覆盖率 98.1%
  • 代码审核:99/100,0 安全漏洞,风险「低」
  • 编译:0 错误、0 相关警告(5 个已有警告与本次无关);4 个 deb 包构建成功

验证建议

在默认应用设置模块中:验证设置默认应用和移除自定义应用功能正常;验证传入不存在的应用 ID 时为空操作。


Multica Issue: DDE-158 (id: 6a561c87-65c5-42c4-b918-d950d5798691)

Note — stacked PR: This PR is based on the head of #3421 (DDE-104, which adds the unit-test infrastructure including tests/ut_categorymodel.cpp). When #3421 merges first, this PR automatically collapses to show only the single DDE-158 fix commit.

为 dde-control-center 补充 12 个核心单元的 GTest 单元测试,覆盖 DCCLocale、MetaData、KeyboardModel、DockPluginSortProxyModel、CategoryModel 等模块。新增 12 个测试文件并修改 tests/CMakeLists.txt,共 13 个文件、334 个用例(331 passed / 0 failed / 3 skipped)。

覆盖率:行 98.6% / 函数 98.9% / 分支 66.6%,均达交付达标线(函数 >80% / 行 >80% / 分支尽可能高)。

关联 issue: DDE-104
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: MyLeeJiEun

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

@deepin-ci-robot

Copy link
Copy Markdown

Hi @MyLeeJiEun. Thanks for your PR.

I'm waiting for a linuxdeepin member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@sourcery-ai

sourcery-ai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fixes CategoryModel::getAppById by avoiding pointers and iterators into temporary QList values, then updates callers and tests to use value semantics; the stacked PR also introduces the shared unit-test target, extensive component coverage, and an optional gcovr coverage report.

Sequence diagram for safe app lookup and default-app operations

sequenceDiagram
    participant Caller
    participant CategoryModel
    participant Category
    participant App

    Caller->>CategoryModel: removeApp(id) or setDefaultApp(id)
    CategoryModel->>Category: getappItem()
    Category-->>CategoryModel: QList<App> by value
    CategoryModel->>CategoryModel: getAppById(id)
    CategoryModel->>App: isValid(app)
    alt app found
        CategoryModel-->>Caller: requestDelUserApp(category, app) or requestSetDefaultApp(category, app)
    else app not found
        CategoryModel-->>Caller: no-op
    end
Loading

File-Level Changes

Change Details Files
Eliminate dangling-pointer and temporary-iterator undefined behavior in app lookup.
  • Bind the value-returned app list to one local container before searching.
  • Return the matching App by value and use a default App with an empty ID as the not-found sentinel.
  • Update removal and default-selection callers to validate and emit the value result directly.
src/plugin-defaultapp/operation/categorymodel.cpp
src/plugin-defaultapp/operation/categorymodel.h
Add and integrate broad unit-test coverage for the project components, including the corrected app lookup behavior.
  • Expand the unit-test target with component sources, Qt/ICU dependencies, per-file access overrides, and coverage instrumentation/reporting.
  • Add tests covering CategoryModel lookup, no-op, signal, model, and sorting behavior.
  • Add tests for default-app Category plus keyboard, locale, metadata, keyfile, gesture, Bluetooth, audio, dock sorting, and timezone utilities.
tests/CMakeLists.txt
tests/ut_category.cpp
tests/ut_categorymodel.cpp
tests/ut_dcclocale.cpp
tests/ut_metadata.cpp
tests/ut_timezone_map_util.cpp
tests/ut_keyfile.cpp
tests/ut_gesturedata.cpp
tests/ut_bluetoothdevice.cpp
tests/ut_keyboardmodel.cpp
tests/ut_dockpluginsortproxymodel.cpp
tests/ut_port.cpp
tests/ut_sounddevicedata.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

1. getappItem() returned QList<App> by value, creating temporaries
2. getAppById iterated three different temporaries via begin/end/cend
3. The returned const App* pointed into a destroyed temporary (UB)
4. Return const QList<App>& referencing member m_applist to fix it

Influence:
1. Verify setting/removing default apps works in default-app plugin
2. Verify setDefaultApp/removeApp with a non-existent id is a no-op
3. Run unit-test binary: all 66 cases must pass

fix: getappItem 改为引用返回以消除悬垂指针

1. getappItem() 原按值返回 QList<App>,产生临时对象
2. getAppById 通过 begin/end/cend 迭代了三个不同临时容器
3. 返回的 const App* 指向已销毁的临时对象,属未定义行为
4. 改为返回 const QList<App>& 引用成员 m_applist 以修复

Influence:
1. 验证默认应用插件中设置/移除默认应用功能正常
2. 验证 setDefaultApp/removeApp 传入不存在的 id 时为空操作
3. 运行单元测试:全部 66 个用例须通过
@MyLeeJiEun
MyLeeJiEun force-pushed the fix/dde-158-getappbyid-value-return branch from 114f855 to b973e66 Compare August 27, 2026 04:02
@MyLeeJiEun MyLeeJiEun changed the title fix: return App by value in getAppById fix: return QList by ref in getappItem Aug 27, 2026
@deepin-bot

deepin-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 6.1.106
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #3497

@deepin-bot

deepin-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 6.1.107
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #3503

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants