Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On Windows, avoid panic in video_modes() #3255

Merged

Conversation

fornwall
Copy link
Contributor

@fornwall fornwall commented Dec 4, 2023

We are seeing a panic due to the unwrap() usage in video_modes() in crash reporting.

Haven't been able to reproduce it ourselves, but in general GetMonitorInfoW can fail, so this changes MonitorHandle::video_modes() to return a Result.

Copy link
Member

@kchibisov kchibisov left a comment

Choose a reason for hiding this comment

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

Just return an empty iterator, it's already a thing with some backends.

@kchibisov kchibisov added this to the Version 0.29.5 milestone Dec 4, 2023
@daxpedda daxpedda removed their request for review December 4, 2023 12:43
@fornwall
Copy link
Contributor Author

fornwall commented Dec 4, 2023

Just return an empty iterator, it's already a thing with some backends.

👍 I'll go ahead and update the PR with that.

@daxpedda
Copy link
Member

daxpedda commented Dec 4, 2023

Just return an empty iterator, it's already a thing with some backends.

Isn't this just a problem with Windows returning an error and Winit exposing that to the user? I agree that if the error is just not finding any windows we should indeed return an empty iterator. But if there is an error we can return to the user shouldn't we do that?

Though it seems to exclusively affect Windows.

@fornwall did your crash reports show what kind of error Windows is returning here? Because the documentation doesn't mention what kind of error this function can return.

@fornwall
Copy link
Contributor Author

fornwall commented Dec 4, 2023

did your crash reports show what kind of error Windows is returning here?

This is the available information:

panic: called Result::unwrap() on an Err value: Os { code: 1461, kind: Uncategorized, message: "Invalid monitor handle." }

Windows 11(22621), Intel(R) UHD Graphics 630, driver version 0.404.2111.

@daxpedda
Copy link
Member

daxpedda commented Dec 4, 2023

I know absolutely nothing about the Windows backend, but doesn't that point to a bug in Winit then?

@daxpedda
Copy link
Member

daxpedda commented Dec 4, 2023

Taking a look at the HMONITOR documentation, my first guess would be that the monitor handle might have been invalidated by the time this function is called.

So, again, I know nothing about the Windows backend or the API, my guess would be that if we can check the validity of the handle we should do that first, if not, we can check for that error. In either case an empty iterator would be better then returning a Result.

@fornwall fornwall changed the title Avoid unwrap in video_modes() On Windows, avoid panic in video_modes() Dec 4, 2023
@fornwall
Copy link
Contributor Author

fornwall commented Dec 4, 2023

Should we move the get_monitor_info() call out of the loop?

@fornwall
Copy link
Contributor Author

fornwall commented Dec 4, 2023

Just return an empty iterator, it's already a thing with some backends.

Updated to log an error and break out of the loop now.

@kchibisov
Copy link
Member

Can't we write something like that

diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs
index f1313970..1548bc05 100644
--- a/src/platform_impl/windows/monitor.rs
+++ b/src/platform_impl/windows/monitor.rs
@@ -230,44 +230,45 @@ impl MonitorHandle {
         // EnumDisplaySettingsExW can return duplicate values (or some of the
         // fields are probably changing, but we aren't looking at those fields
         // anyway), so we're using a BTreeSet deduplicate
-        let mut modes = BTreeSet::new();
-        let mut i = 0;
+        let mut modes = BTreeSet::<RootVideoMode>::new();
+        let mod_map = |mode: RootVideoMode| mode.video_mode;
+
+        let monitor_info = match get_monitor_info(self.0) {
+            Ok(monitor_info) => monitor_info,
+            Err(error) => {
+                log::warn!("Error from get_monitor_info: {error}");
+                return modes.into_iter().map(mod_map);
+            }
+        };
+
+        let device_name = monitor_info.szDevice.as_ptr();
 
+        let mut i = 0;
         loop {
-            unsafe {
-                match get_monitor_info(self.0) {
-                    Ok(monitor_info) => {
-                        let device_name = monitor_info.szDevice.as_ptr();
-                        let mut mode: DEVMODEW = mem::zeroed();
-                        mode.dmSize = mem::size_of_val(&mode) as u16;
-                        if EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == false.into() {
-                            break;
-                        }
-                        i += 1;
-
-                        const REQUIRED_FIELDS: u32 =
-                            DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
-                        assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
-
-                        // Use Ord impl of RootVideoMode
-                        modes.insert(RootVideoMode {
-                            video_mode: VideoMode {
-                                size: (mode.dmPelsWidth, mode.dmPelsHeight),
-                                bit_depth: mode.dmBitsPerPel as u16,
-                                refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
-                                monitor: self.clone(),
-                                native_video_mode: Box::new(mode),
-                            },
-                        });
-                    }
-                    Err(error) => {
-                        log::warn!("Error from get_monitor_info: {error}");
-                        break;
-                    }
-                }
+            let mut mode: DEVMODEW = unsafe { mem::zeroed() };
+            mode.dmSize = mem::size_of_val(&mode) as u16;
+            if unsafe { EnumDisplaySettingsExW(device_name, i, &mut mode, 0) } == false.into() {
+                break;
             }
+
+            const REQUIRED_FIELDS: u32 =
+                DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
+            assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
+
+            // Use Ord impl of RootVideoMode
+            modes.insert(RootVideoMode {
+                video_mode: VideoMode {
+                    size: (mode.dmPelsWidth, mode.dmPelsHeight),
+                    bit_depth: mode.dmBitsPerPel as u16,
+                    refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
+                    monitor: self.clone(),
+                    native_video_mode: Box::new(mode),
+                },
+            });
+
+            i += 1;
         }
 
-        modes.into_iter().map(|mode| mode.video_mode)
+        modes.into_iter().map(mod_map)
     }
 }

@fornwall
Copy link
Contributor Author

fornwall commented Dec 6, 2023

Can't we write something like that

@kchibisov Looks like a good improvement -have update the PR with that!

@kchibisov kchibisov merged commit b863283 into rust-windowing:master Dec 6, 2023
50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants