luxonis / XLink

A cross-platform library for communicating with devices over various physical links.
Apache License 2.0
11 stars 17 forks source link

XLink crashes app if any exceptions occur in XLink cpp files #59

Open diablodale opened 1 year ago

diablodale commented 1 year ago

Xlink can easily crash due to the newer C++ code within it yet having no exception handling.

For example, the file usb_host.cpp has a lot of C++ code in it and most of it can throw exceptions. However, there is no exception handling in functions, e.g. getUSBDevices(), to prevent their doomed cascade upward into standard-C code...which causes an app crash/segfault. 💥

Since exceptions can be throw by trivial uses like std::string(...) or container::at(), I recommend that the top-level functions in all cpp files be marked noexcept. Then add try/catch to all needed places to catch any exceptions and return relevant error codes instead. All modern editors like vscode and analyzers provide clear feedback of functions marked noexcept yet calling things which can throw.

Setup

Repro

  1. Edit usb_host.cpp and insert as the first line of the function getUSBDevices() the following: throw std::runtime_error("crash me");
  2. config and build. I used VS2019.
  3. attach a USB oak device
  4. run tests/color_camera_node_test

Result

App immediately crashes.

Expected

App somehow gracefully fails, in this case it should report it could find no usb devices.

themarpe commented 1 year ago

This will likely best be addressed (for now) by doing function wide try/catch, until either the lib is C++ified or if more fine grained handling is added (though I assume most cases will be general issues, as libusb ret value handling is already fine grained)

diablodale commented 1 year ago

Got it. I will look into this late this week or next.

I intend to mark functions noexcept at the C/C++ boundaries. Also, use normal try/catch where needed around large blocks of code.

I hesitate in using the function-level try/catch as it is intended to be used on constructors since that's the only way to catch exceptions in their initializer lists. It is a rare language feature that dev's might overlook or not understand.

diablodale commented 1 year ago

This is straightforward though needs supporting code. In XLink today, I saw how I need to handle libusb while managing exceptions. For example...

  1. refLibusbDeviceByName()
  2. libusb_get_device_list(context, &devs) populates the device list
  3. getLibusbDevicePath()
  4. getLibusbDevicePath() throws exception

The exception handling has to be in refLibusbDeviceByName() because the handler needs to libusb_free_device_list(devs, 1)

Tracing all the code to find where to insert try/catch/freeResources is time consuming and error-prone. I recommend using a RAII wrapper; similar to a unique_ptr with custom deleter. I would prefer:

diablodale commented 1 year ago

At https://github.com/diablodale/XLink/commit/2cfde5b58ebce9cc5769dc84ce54f3eb632a901b is a wrapper class LibusbDeviceList for libusb_get_device_list/free. It is an STL container and can be used with language/library features. Changed XLink to use it and is good in casual testing.

TODO

  1. Need wrapper for libusb_open/close, libusb_get_config_descriptor/free, maybe libusb_claim_interface/release.
  2. Add the try/catch at the C/C++ boundaries as a minimum. Might have them in nested functions where there is value in having more specific error code/returns.
  3. Choose if wrapper(s) use the factory approach like above commit...or use a constructor approach?

Factory approach (implemented in above commit)

Constructor approach (alternative)

diablodale commented 1 year ago

https://github.com/diablodale/XLink/commit/461e30ad4fbb437e6ec9dfd7df1237b326a22ea3 adds the Constructor-approach. I think I like this Constructor-approach more. It feels more "STL" where containers are created with constructors (not factories).

However, I also updated the Factory-approach to be non-throwing. The Caller does not need try/catch and can always test the result with if (!myuniqueptr) { handle_error() }

Any feedback on which approach is preferred?

diablodale commented 1 year ago

added PR to fix this issue. I need a few questions answered in the PR to move further. :-)

diablodale commented 1 year ago

I've pushed the last commit to draft PR above. It includes markdown and mermaid documentation for usage and object model for the libusb wrapper. Its really quite simple. 🔢 This wrapper replaces much of the old Xlink C-style code for its usb transport and fixes this issue.

Moving forward, this issue's fixes and more will usually be in my fork at https://github.com/diablodale/XLink/ I'll leave this issue open as the Luxonis fork does not have any fixes for this issue's crashes.