Skip to content

Fix Simulator crash with empty library#2433

Closed
dnicolson wants to merge 0 commit intoProvenance-Emu:developfrom
dnicolson:patch-1
Closed

Fix Simulator crash with empty library#2433
dnicolson wants to merge 0 commit intoProvenance-Emu:developfrom
dnicolson:patch-1

Conversation

@dnicolson
Copy link
Contributor

@dnicolson dnicolson commented Nov 29, 2025

User description

This fixes a crash because a Realm instance is expected:

self.game = game.isFrozen ? game : game.freeze()

This only occurs in the Simulator with an empty library:

if safeGames.isEmpty && AppState.shared.isSimulator {


PR Type

Bug fix


Description

  • Fix Simulator crash when library is empty

  • Ensure Realm instance exists in mock game generation

  • Wrap mock game creation in Realm write transaction

  • Properly add generated games to Realm database


Diagram Walkthrough

flowchart LR
  A["mockGenerate method"] --> B["Create in-memory Realm"]
  B --> C["Write transaction"]
  C --> D["Create mock games"]
  D --> E["Add to Realm"]
  E --> F["Return games array"]
Loading

File Walkthrough

Relevant files
Bug fix
PVGame.swift
Add Realm transaction to mock game generation                       

PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVGame.swift

  • Initialize in-memory Realm instance in mockGenerate method
  • Wrap mock game creation in realm.write transaction block
  • Add created games to Realm using realm.add(game)
  • Fix publishDate to use current index instead of count
+14/-7   

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Unhandled fatal errors

Description: Force-unwrapping Realm initialization and write transaction with 'try!' can crash the app
(denial of service) if in-memory Realm configuration fails or a write error occurs;
replace with handled 'try' and error propagation.
PVGame.swift [115-130]

Referred Code
let realm = try! Realm(configuration: .init(inMemoryIdentifier: "MockRealm"))
let systemIdentifier = systemID ?? "mock.system"

var games: [PVGame] = []
try! realm.write {
    for index in 1...count {
        let game = PVGame()
        game.title = "Mock Game \(index)"
        game.systemIdentifier = systemIdentifier
        game.md5Hash = UUID().uuidString // Mock MD5 hash
        game.publishDate = "\(1980 + index)"
        realm.add(game)
        games.append(game)
    }
}
return games
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Forced try usage: The code uses try! to create an in-memory Realm and to perform a write transaction without
handling potential initialization or write errors.

Referred Code
let realm = try! Realm(configuration: .init(inMemoryIdentifier: "MockRealm"))
let systemIdentifier = systemID ?? "mock.system"

var games: [PVGame] = []
try! realm.write {
    for index in 1...count {
        let game = PVGame()
        game.title = "Mock Game \(index)"
        game.systemIdentifier = systemIdentifier
        game.md5Hash = UUID().uuidString // Mock MD5 hash
        game.publishDate = "\(1980 + index)"
        realm.add(game)
        games.append(game)
    }
}
return games

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No audit logs: The creation and persistence of mock games into Realm are not logged, which may omit audit
trails of write operations even in test/simulator contexts.

Referred Code
var games: [PVGame] = []
try! realm.write {
    for index in 1...count {
        let game = PVGame()
        game.title = "Mock Game \(index)"
        game.systemIdentifier = systemIdentifier
        game.md5Hash = UUID().uuidString // Mock MD5 hash
        game.publishDate = "\(1980 + index)"
        realm.add(game)
        games.append(game)
    }
}
return games

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unvalidated inputs: The method writes mock data into Realm without validating external inputs like systemID or
count bounds, which could lead to issues if misused outside strictly test contexts.

Referred Code
public static func mockGenerate(systemID: String? = nil, count: Int = 10) -> [PVGame] {
    let realm = try! Realm(configuration: .init(inMemoryIdentifier: "MockRealm"))
    let systemIdentifier = systemID ?? "mock.system"

    var games: [PVGame] = []
    try! realm.write {
        for index in 1...count {
            let game = PVGame()
            game.title = "Mock Game \(index)"
            game.systemIdentifier = systemIdentifier
            game.md5Hash = UUID().uuidString // Mock MD5 hash
            game.publishDate = "\(1980 + index)"
            realm.add(game)
            games.append(game)
        }
    }
    return games
}

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Isolate in-memory Realm instances

The mockGenerate function uses a static inMemoryIdentifier, causing the same
Realm instance to be reused, which can lead to state leakage. Use a unique
identifier like UUID().uuidString for each call to ensure isolation.

Examples:

PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVGame.swift [115]
        let realm = try! Realm(configuration: .init(inMemoryIdentifier: "MockRealm"))

Solution Walkthrough:

Before:

public static func mockGenerate(systemID: String? = nil, count: Int = 10) -> [PVGame] {
    let realm = try! Realm(configuration: .init(inMemoryIdentifier: "MockRealm"))
    
    var games: [PVGame] = []
    try! realm.write {
        for index in 1...count {
            let game = PVGame()
            // ...
            realm.add(game)
            games.append(game)
        }
    }
    return games
}

After:

public static func mockGenerate(systemID: String? = nil, count: Int = 10) -> [PVGame] {
    let realm = try! Realm(configuration: .init(inMemoryIdentifier: UUID().uuidString))
    
    var games: [PVGame] = []
    try! realm.write {
        for index in 1...count {
            let game = PVGame()
            // ...
            realm.add(game)
            games.append(game)
        }
    }
    return games
}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a significant design flaw where using a static inMemoryIdentifier can lead to state leakage in tests and previews, proposing a robust solution.

Medium
Possible issue
Use a unique in-memory identifier

To ensure test isolation, replace the hardcoded in-memory Realm identifier with
a unique one, such as UUID().uuidString, for each call to mockGenerate.

PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVGame.swift [115]

-let realm = try! Realm(configuration: .init(inMemoryIdentifier: "MockRealm"))
+let realm = try! Realm(configuration: .init(inMemoryIdentifier: UUID().uuidString))
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that a hardcoded Realm identifier can cause state pollution between tests, and proposing a unique ID for each call significantly improves test isolation and reliability.

Medium
Avoid force unwrapping with try!

Replace try! with a do-catch block to handle potential Realm errors gracefully
and prevent the application from crashing during mock data generation.

PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVGame.swift [115-130]

-let realm = try! Realm(configuration: .init(inMemoryIdentifier: "MockRealm"))
 let systemIdentifier = systemID ?? "mock.system"
+do {
+    let realm = try Realm(configuration: .init(inMemoryIdentifier: "MockRealm"))
+    var games: [PVGame] = []
+    try realm.write {
+        for index in 1...count {
+            let game = PVGame()
+            game.title = "Mock Game \(index)"
+            game.systemIdentifier = systemIdentifier
+            game.md5Hash = UUID().uuidString // Mock MD5 hash
+            game.publishDate = "\(1980 + index)"
+            realm.add(game)
+            games.append(game)
+        }
+    }
+    return games
+} catch {
+    print("Failed to generate mock games: \(error)")
+    return []
+}
 
-var games: [PVGame] = []
-try! realm.write {
-    for index in 1...count {
-        let game = PVGame()
-        game.title = "Mock Game \(index)"
-        game.systemIdentifier = systemIdentifier
-        game.md5Hash = UUID().uuidString // Mock MD5 hash
-        game.publishDate = "\(1980 + index)"
-        realm.add(game)
-        games.append(game)
-    }
-}
-return games
-
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out the risk of crashing with try!, but its impact is moderate as this is a mock data generator where failing fast can be an acceptable design choice.

Low
  • More

Copilot AI review requested due to automatic review settings March 7, 2026 00:03
Copy link
Contributor

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@JoeMatt
Copy link
Member

JoeMatt commented Mar 7, 2026

@claude please review this ticket, we've made some movements towards getting ready for a switch from realm to swiftdata, make sure this fix still applies and if we need a swiftdata version too

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Analysis Complete

Summary

  • Realm fix (PR's existing change): still applies cleanly to develop
  • SwiftData mockGenerate: added to Game_Data and pushed to Provenance-Emu/Provenance:patch-1
  • Analysis comment posted on PR

Details

  • The crash still exists on develop (PVGame.mockGenerate creates objects outside any Realm, then GameContextMenu calls .freeze() on them)
  • PR fix is correct and still needed
  • Game_Data.mockGenerate added for SwiftData path (no .freeze() needed in SwiftData)
  • ConsoleGamesView migration to Game_Data is out of scope for this PR

@JoeMatt JoeMatt removed the request for review from jasarien March 7, 2026 00:06
@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Analysis: Fix Review + SwiftData Support

Realm Fix (existing PR change)

The fix in patch-1 still applies cleanly to develop. The root cause is unchanged:

Crash path:

  1. ConsoleGamesView.gamesSection() calls PVGame.mockGenerate() when library is empty in Simulator
  2. Mock PVGame objects are passed to GameContextMenu
  3. GameContextMenu.init calls game.isFrozen ? game : game.freeze() (line 39 of GameContextMenu.swift)
  4. Realm.freeze() crashes on objects never inserted into any Realm

Fix: Wrap mockGenerate in an in-memory Realm write transaction. The PR fix is correct; develop still has the crash, so this PR is still needed. Also fixes a minor bug: publishDate used count instead of index.


SwiftData Support Added

Game_Data (SwiftData model in PVLibrary/Sources/PVLibrary/SwiftData/Game_Data.swift) had no mockGenerate method. I have added one and pushed it to Provenance-Emu/Provenance:patch-1.

SwiftData @Model objects have no .freeze() concept — they can be passed directly to views without a ModelContext. No transaction wrapper needed. The implementation is simply:

static func mockGenerate(systemID: String? = nil, count: Int = 10) -> [Game_Data] {
    let systemIdentifier = systemID ?? "mock.system"
    return (1...count).map { index in
        Game_Data(
            title: "Mock Game \(index)",
            systemIdentifier: systemIdentifier,
            md5Hash: UUID().uuidString,
            publishDate: "\(1980 + index)"
        )
    }
}

What remains for full SwiftData migration

ConsoleGamesView.gamesSection() still calls PVGame.mockGenerate (Realm path). When the view migrates to use Game_Data, it will call Game_Data.mockGenerate instead, and GameContextMenu will need to accept Game_Data rather than PVGame. That broader migration is out of scope for this PR.

The SwiftData commit is on Provenance-Emu/Provenance:patch-1 — please cherry-pick into the fork branch or merge directly.

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.

3 participants