Add a decorator to prevent Exceptions (to prevent segfaults).

This commit is contained in:
Florian Bruhin 2014-09-16 22:06:48 +02:00
parent 5eef6d422d
commit 644dfe53e2
2 changed files with 67 additions and 24 deletions

View File

@ -30,7 +30,8 @@ from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config
from qutebrowser.network import networkmanager
from qutebrowser.utils import message, usertypes, log, http, jinja, qtutils
from qutebrowser.utils import (message, usertypes, log, http, jinja, qtutils,
utils)
class BrowserPage(QWebPage):
@ -223,6 +224,9 @@ class BrowserPage(QWebPage):
"""
return ext in self._extension_handlers
# WORKAROUND for:
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html
@utils.prevent_exceptions(False, PYQT_VERSION < 0x50302)
def extension(self, ext, opt, out):
"""Override QWebPage::extension to provide error pages.
@ -235,28 +239,11 @@ class BrowserPage(QWebPage):
Handler return value.
"""
try:
try:
handler = self._extension_handlers[ext]
except KeyError:
log.webview.warning("Extension {} not supported!".format(ext))
return super().extension(ext, opt, out)
return handler(opt, out)
except: # pylint: disable=bare-except
if PYQT_VERSION >= 0x50302:
raise
else:
# WORKAROUND:
#
# Due to a bug in PyQt, exceptions inside extension() get
# swallowed:
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html
#
# We used to re-raise the exception with a single-shot QTimer
# here, but that lead to a strange proble with a KeyError with
# some random jinja template stuff as content. For now, we only
# log it, so it doesn't pass 100% silently.
log.webview.exception("Error inside WebPage::extension")
return False
handler = self._extension_handlers[ext]
except KeyError:
log.webview.warning("Extension {} not supported!".format(ext))
return super().extension(ext, opt, out)
return handler(opt, out)
def javaScriptAlert(self, _frame, msg):
"""Override javaScriptAlert to use the statusbar."""

View File

@ -35,7 +35,7 @@ from PyQt5.QtGui import QKeySequence, QColor
import pkg_resources
import qutebrowser
from qutebrowser.utils import qtutils
from qutebrowser.utils import qtutils, log
def elide(text, length):
@ -494,3 +494,59 @@ def disabled_excepthook():
# unchanged. Otherwise, we reset it.
if sys.excepthook is sys.__excepthook__:
sys.excepthook = old_excepthook
class prevent_exceptions: # pylint: disable=invalid-name
"""Decorator to ignore and log exceptions.
This needs to be used for some places where PyQt segfaults on exceptions or
silently ignores them.
We used to re-raise the exception with a single-shot QTimer in a similiar
case, but that lead to a strange proble with a KeyError with some random
jinja template stuff as content. For now, we only log it, so it doesn't
pass 100% silently.
This could also be a function, but as a class (with a "wrong" name) it's
much cleaner to implement.
Attributes:
retval: The value to return in case of an exception.
predicate: The condition which needs to be True to prevent exceptions
"""
def __init__(self, retval, predicate=True):
"""Save decorator arguments.
Gets called on parse-time with the decorator arguments.
Args:
See class attributes.
"""
self.retval = retval
self.predicate = predicate
def __call__(self, func):
"""Gets called when a function should be decorated.
Args:
func: The function to be decorated.
Return:
The decorated function.
"""
if not self.predicate:
return func
retval = self.retval
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except BaseException:
log.misc.exception("Error in {}".format(func.__qualname__))
return retval
return wrapper