QWebEngineCallback - еще один способ изощренного отстрела ноги

1 мин на чтение

В C++ имеется изрядное количество элегантных и не очень способов отстрела ног. Представляю неочевидный (на первый взгляд) и довольно интересный способ, который всплыл на проекте во время тестирования.

Имеется гибридное Qt приложение, задача довольно экстравагантна: вызываем функцию в js, в callback получаем результат и смотрим, что там получилось. В общем, как обычно, внедряли новый функционал и тестили что получилось.

Внезапно начали падать на одном тестовом стенде.

На поиск источника ошибки с момента первой регистрации проблемы ушла почти неделя пассивных поисков. “Упало в недрах QWebEngine при обработке отложенных событий. Воспроизвести не удалось.” - так удавалось отмазываться от глубоких копаний в проблеме, пока случаи падений были единичными.

Но вскоре проблема участилась. Краш-репорты не давали никаких конкретных намеков: в модуль Qt5WebEngineCore попало событие, у которого QEventPrivate * d = nullptr. Откуда это событие пошло выяснить не удалось.

Содержимое QEvent * e из дампа:

-		e	0x203cd270 {d=0x00000000 <NULL> t=1000 posted=0 ...}	QEvent * {Qt5WebEngineCore.dll!QEvent}
+		[QEvent]	{d=0x00000000 <NULL> t=1000 posted=0 ...}	Qt5WebEngineCore.dll!QEvent
+		__vfptr	0x03e3f4f8 {Qt5WebEngineCore.dll!const QEvent::`local vftable'} {0x019ad400 {Qt5WebEngineCore.dll!QEvent::`scalar deleting destructor'(unsigned int)}}	void * *
		d	0x00000000 <NULL>	QEventPrivate *
		t	1000	unsigned short
		posted	0	unsigned short
		spont	0	unsigned short
		m_accept	1	unsigned short
		reserved	1434	unsigned short

Что это, как оно тут оказалось? Мы такого не писали!

С трудом, нам удалось локализовать участок кода, который приводил к проблеме. Он обладал изюминкой: от результата выполнения js зависела обработка некого event'a:

class WebView: public QWebEngineView {
    ...
    bool event(QEvent * event){
        view->page()->runJavaScript("somePowerfullFunc(someData)", [this, event](const QVariant & result){
            if(result.toBool()){
                event->accepted();
            } else {
                event->ignored();
            }
        });
    }
    ...
};

Работаем с event‘ом, замечательно. А кто сказал, что он еще валиден? Кто знает, что с ним успело произойти, пока у нас не выполнился callback?

Важный момент тут в том, что в дебаге все работало замечательно. А релиз проверит кто-нибудь другой нам надо фичи клепать, а не тестировать разные конфигурации запуска. Но в релизе мы крашились через раз в недрах QWebEngine и ни одного намека на этот callback в стеке естественно не было.

У нас же есть ревью! Почему это не увидели? Все просто. Потому что запушили большой кусок кода, пока смотрели, глаз замылился. Да и вообще, кто же знал, что так делать нельзя?

Это еще одно подтверждение тому, что после любых изменений (даже самых незначительных) может случиться деградация функционала в совершенно неожиданных местах. Причем, выявить источник проблемы, чем дальше от изменения, тем сложнее.

Дата изменения: