Skip to content

fix: guard role-to-enum casts in models to prevent RangeDefect crashes#21116

Open
jrainville wants to merge 1 commit into
release/2.38.xfrom
fix/crash-on-section-switching
Open

fix: guard role-to-enum casts in models to prevent RangeDefect crashes#21116
jrainville wants to merge 1 commit into
release/2.38.xfrom
fix/crash-on-section-switching

Conversation

@jrainville
Copy link
Copy Markdown
Member

What does the PR do

Fixes #21085

Summary

This PR hardens QML list-model role handling to prevent crashes caused by out-of-range role-to-enum conversions.

The crash signature reported was a Nim RangeDefect:

  • value out of range: 295 notin 257 .. 293

This was consistent with enum casting in model data methods when Qt requested an unexpected role during view/model lifecycle transitions.

Root Cause

Several model data methods converted an incoming integer role directly to an enum type without validating bounds first, for example:

  • let enumRole = role.ModelRole

When role fell outside low(EnumType)..high(EnumType), Nim raised RangeDefect, which propagated through Qt event handling and triggered app shutdown.

What Changed

Added defensive guards before all detected role-to-enum casts in the touched module scope:

  • if role < ord(low(EnumType)) or role > ord(high(EnumType)):
    • return

This keeps behavior unchanged for valid roles and safely ignores unknown/out-of-range roles.

Scope

  • 69 files changed
  • 209 insertions
  • No removals

Key hotspot addressed

  • section_model.nim

Also covered

  • Main module list models (chat, wallet, communities, app search, market, activity center, stickers, profile)
  • Shared models
  • Onboarding model
  • Shared keycard popup model

Why This Is Safe

  • Guard only affects invalid role values.
  • Valid role handling and returned data are unchanged.
  • Prevents exceptions from escaping Qt event handlers under transient/stale role access patterns.

Validation

  • Verified guard coverage count matches cast-site count in the targeted scope:
    • role-to-enum cast sites: 69
    • inserted bounds guards: 69

Testing

  • No runtime behavior changes expected for normal role paths; this is defensive crash-prevention logic.

Risk

Low

@jrainville jrainville requested a review from a team as a code owner June 2, 2026 18:42
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This one is the main culprit, since the enum starts at UserRole + 1.
It has 37 entries.
So valid values are 257..293 (In Qt/Nim QAbstractItemModel roles, UserRole is typically 256).
The crash popoup says value out of range: 295 notin 257 .. 293, which is an exact match for this enum cast failure.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses #21085 by hardening Nim/QML QAbstractListModel.data() implementations against RangeDefect crashes when Qt requests an unexpected (out-of-range) role integer and the code casts it directly to an enum.

Changes:

  • Added low(EnumType)..high(EnumType) bounds checks before role-to-enum casts across affected list models.
  • Ensured out-of-range roles are ignored early (returning an empty/invalid QVariant) instead of raising exceptions.
  • Added a clarifying comment in section_model.nim explaining the Qt/proxy lifecycle motivation for ignoring unknown roles.

Reviewed changes

Copilot reviewed 69 out of 69 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/app/modules/main/activity_center/model.nim Guard NotifRoles role-to-enum casting in data()
src/app/modules/main/app_search/models/chat_search_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/app_search/models/location_menu_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/app_search/models/location_menu_sub_model.nim Guard SubModelRole role-to-enum casting in data()
src/app/modules/main/app_search/models/result_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/browser_section/bookmark/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/browser_section/dapps/accounts.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/browser_section/dapps/dapps.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/chat_section/chat_content/input_area/urls_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/chat_section/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/communities/models/curated_community_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/communities/models/discord_categories_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/communities/models/discord_channels_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/communities/models/discord_file_list_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/communities/models/discord_import_errors_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/communities/models/discord_import_tasks_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/communities/tokens/models/token_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/communities/tokens/models/token_owners_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/ephemeral_notification_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/market_section/market_leaderboard_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/contacts/models/showcase_contact_accounts_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/contacts/models/showcase_contact_generic_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/contacts/models/showcase_contact_social_links_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/devices/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/ens_usernames/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/notifications/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/profile/models/showcase_preferences_generic_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/profile/models/showcase_preferences_social_links_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/sync/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/profile_section/wallet/accounts/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/stickers/models/sticker_list.nim Guard StickerRoles role-to-enum casting in data()
src/app/modules/main/stickers/models/sticker_pack_list.nim Guard StickerPackRoles role-to-enum casting in data()
src/app/modules/main/wallet_section/accounts/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/activity/collectibles_model.nim Guard CollectibleRole role-to-enum casting in data()
src/app/modules/main/wallet_section/activity/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/activity/recipients_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/all_tokens/token_groups_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/all_tokens/token_lists_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/all_tokens/tokens_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/assets/balances_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/assets/grouped_account_assets_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/buy_sell_crypto/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/following_addresses/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/networks/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/networks/rpc_providers_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/saved_addresses/model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/send/network_route_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/send/suggested_route_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/main/wallet_section/send_new/path_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/onboarding/models/login_account_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/collectible_ownership_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/collectible_trait_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/collectibles_model.nim Guard CollectibleRole role-to-enum casting in data()
src/app/modules/shared_models/contract_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/derived_address_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/keypair_account_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/keypair_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/link_preview_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/member_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/message_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/message_reaction_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/payment_request_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/section_model.nim Guard ModelRole role-to-enum casting in data() (with explanatory comment)
src/app/modules/shared_models/token_criteria_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/token_list_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/token_permission_chat_list_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/token_permissions_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_models/user_model.nim Guard ModelRole role-to-enum casting in data()
src/app/modules/shared_modules/keycard_popup/models/keycard_model.nim Guard ModelRole role-to-enum casting in data()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/app/modules/main/communities/models/discord_channels_model.nim
Comment thread src/app/modules/main/communities/models/discord_file_list_model.nim
@status-im-auto
Copy link
Copy Markdown
Member

status-im-auto commented Jun 2, 2026

Jenkins Builds

Click to see older builds (5)
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ c89ca23 1 2026-06-02 18:51:46 ~9 min tests/nim 📄log
✔️ c89ca23 1 2026-06-02 18:53:24 ~10 min android/arm64 🤖apk 📲
✔️ c89ca23 1 2026-06-02 18:54:30 ~11 min tests/ui 📄log
✔️ c89ca23 1 2026-06-02 18:55:20 ~12 min ios/aarch64 📱ipa 📲
✖️ c89ca23 3650 2026-06-02 20:16:17 ~1 hr 22 min tests/e2e-android 📦pkg
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ 83b4893 2 2026-06-02 19:07:20 ~7 min tests/nim 📄log
✔️ 83b4893 2 2026-06-02 19:09:33 ~10 min android/arm64 🤖apk 📲
✔️ 83b4893 2 2026-06-02 19:12:27 ~12 min tests/ui 📄log
✔️ 83b4893 2 2026-06-02 19:12:28 ~13 min ios/aarch64 📱ipa 📲
✔️ 83b4893 2 2026-06-02 19:16:05 ~16 min macos/aarch64 🍎dmg
✔️ 83b4893 2 2026-06-02 19:17:34 ~18 min linux/x86_64 📦tgz
✔️ 83b4893 2 2026-06-02 19:36:30 ~36 min windows/x86_64 💿exe
✔️ 83b4893 11563 2026-06-02 19:39:45 ~22 min tests/e2e 📊rpt
✔️ 83b4893 3497 2026-06-02 19:53:25 ~16 min tests/e2e-windows 📊rpt

Fixes #21085

Add defensive bounds checks before converting Qt role integers to Nim enum roles across list-model data methods.

Return early when role is outside low(EnumRole)..high(EnumRole)
Prevent RangeDefect from bubbling through Qt event handlers during model/delegate lifecycle and tab switches
Apply the same protection pattern consistently across main, shared, onboarding, and keycard popup models
Includes the SectionModel hotspot and all other detected role-to-enum cast sites
@jrainville jrainville force-pushed the fix/crash-on-section-switching branch from c89ca23 to 83b4893 Compare June 2, 2026 18:59
Copy link
Copy Markdown
Member

@caybro caybro left a comment

Choose a reason for hiding this comment

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

Great, I'd just extract the repeating function into some global one like:

proc checkModelIndex(enum: any, role: int): bool

@caybro
Copy link
Copy Markdown
Member

caybro commented Jun 3, 2026

Or, just wrap and expose QAbstractItemModel::checkIndex from qabstractitemmodel.nim

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.

[Desktop] when clicking on tabs on the main nav bar, the app crashed with this error

4 participants