Show More
@@ -27,6 +27,7 b' Authors:' | |||||
27 | # Imports |
|
27 | # Imports | |
28 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
29 |
|
29 | |||
|
30 | import atexit | |||
30 | import glob |
|
31 | import glob | |
31 | import logging |
|
32 | import logging | |
32 | import os |
|
33 | import os | |
@@ -156,6 +157,9 b' class BaseIPythonApplication(Application):' | |||||
156 | """Create a crash handler, typically setting sys.excepthook to it.""" |
|
157 | """Create a crash handler, typically setting sys.excepthook to it.""" | |
157 | self.crash_handler = self.crash_handler_class(self) |
|
158 | self.crash_handler = self.crash_handler_class(self) | |
158 | sys.excepthook = self.crash_handler |
|
159 | sys.excepthook = self.crash_handler | |
|
160 | def unset_crashhandler(): | |||
|
161 | sys.excepthook = sys.__excepthook__ | |||
|
162 | atexit.register(unset_crashhandler) | |||
159 |
|
163 | |||
160 | def _ipython_dir_changed(self, name, old, new): |
|
164 | def _ipython_dir_changed(self, name, old, new): | |
161 | if old in sys.path: |
|
165 | if old in sys.path: |
@@ -38,7 +38,7 b' from IPython.core.excolors import exception_colors' | |||||
38 | has_pydb = False |
|
38 | has_pydb = False | |
39 | prompt = 'ipdb> ' |
|
39 | prompt = 'ipdb> ' | |
40 | #We have to check this directly from sys.argv, config struct not yet available |
|
40 | #We have to check this directly from sys.argv, config struct not yet available | |
41 | if '-pydb' in sys.argv: |
|
41 | if '--pydb' in sys.argv: | |
42 | try: |
|
42 | try: | |
43 | import pydb |
|
43 | import pydb | |
44 | if hasattr(pydb.pydb, "runl") and pydb.version>'1.17': |
|
44 | if hasattr(pydb.pydb, "runl") and pydb.version>'1.17': | |
@@ -64,7 +64,7 b' def BdbQuit_excepthook(et,ev,tb):' | |||||
64 | else: |
|
64 | else: | |
65 | BdbQuit_excepthook.excepthook_ori(et,ev,tb) |
|
65 | BdbQuit_excepthook.excepthook_ori(et,ev,tb) | |
66 |
|
66 | |||
67 | def BdbQuit_IPython_excepthook(self,et,ev,tb): |
|
67 | def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None): | |
68 | print 'Exiting Debugger.' |
|
68 | print 'Exiting Debugger.' | |
69 |
|
69 | |||
70 |
|
70 | |||
@@ -104,8 +104,8 b' class Tracer(object):' | |||||
104 | """ |
|
104 | """ | |
105 |
|
105 | |||
106 | try: |
|
106 | try: | |
107 |
ip = |
|
107 | ip = get_ipython() | |
108 | except: |
|
108 | except NameError: | |
109 | # Outside of ipython, we set our own exception hook manually |
|
109 | # Outside of ipython, we set our own exception hook manually | |
110 | BdbQuit_excepthook.excepthook_ori = sys.excepthook |
|
110 | BdbQuit_excepthook.excepthook_ori = sys.excepthook | |
111 | sys.excepthook = BdbQuit_excepthook |
|
111 | sys.excepthook = BdbQuit_excepthook |
@@ -1447,29 +1447,37 b' class InteractiveShell(SingletonConfigurable, Magic):' | |||||
1447 |
|
1447 | |||
1448 | Set a custom exception handler, which will be called if any of the |
|
1448 | Set a custom exception handler, which will be called if any of the | |
1449 | exceptions in exc_tuple occur in the mainloop (specifically, in the |
|
1449 | exceptions in exc_tuple occur in the mainloop (specifically, in the | |
1450 | run_code() method. |
|
1450 | run_code() method). | |
1451 |
|
1451 | |||
1452 |
|
|
1452 | Parameters | |
|
1453 | ---------- | |||
|
1454 | ||||
|
1455 | exc_tuple : tuple of exception classes | |||
|
1456 | A *tuple* of exception classes, for which to call the defined | |||
|
1457 | handler. It is very important that you use a tuple, and NOT A | |||
|
1458 | LIST here, because of the way Python's except statement works. If | |||
|
1459 | you only want to trap a single exception, use a singleton tuple:: | |||
1453 |
|
1460 | |||
1454 | - exc_tuple: a *tuple* of valid exceptions to call the defined |
|
1461 | exc_tuple == (MyCustomException,) | |
1455 | handler for. It is very important that you use a tuple, and NOT A |
|
|||
1456 | LIST here, because of the way Python's except statement works. If |
|
|||
1457 | you only want to trap a single exception, use a singleton tuple: |
|
|||
1458 |
|
1462 | |||
1459 | exc_tuple == (MyCustomException,) |
|
1463 | handler : callable | |
|
1464 | handler must have the following signature:: | |||
1460 |
|
1465 | |||
1461 | - handler: this must be defined as a function with the following |
|
1466 | def my_handler(self, etype, value, tb, tb_offset=None): | |
1462 | basic interface:: |
|
1467 | ... | |
|
1468 | return structured_traceback | |||
1463 |
|
1469 | |||
1464 | def my_handler(self, etype, value, tb, tb_offset=None) |
|
1470 | Your handler must return a structured traceback (a list of strings), | |
1465 |
|
|
1471 | or None. | |
1466 | # The return value must be |
|
|||
1467 | return structured_traceback |
|
|||
1468 |
|
1472 | |||
1469 | This will be made into an instance method (via types.MethodType) |
|
1473 | This will be made into an instance method (via types.MethodType) | |
1470 | of IPython itself, and it will be called if any of the exceptions |
|
1474 | of IPython itself, and it will be called if any of the exceptions | |
1471 |
listed in the exc_tuple are caught. |
|
1475 | listed in the exc_tuple are caught. If the handler is None, an | |
1472 | internal basic one is used, which just prints basic info. |
|
1476 | internal basic one is used, which just prints basic info. | |
|
1477 | ||||
|
1478 | To protect IPython from crashes, if your handler ever raises an | |||
|
1479 | exception or returns an invalid result, it will be immediately | |||
|
1480 | disabled. | |||
1473 |
|
1481 | |||
1474 | WARNING: by putting in your own exception handler into IPython's main |
|
1482 | WARNING: by putting in your own exception handler into IPython's main | |
1475 | execution loop, you run a very good chance of nasty crashes. This |
|
1483 | execution loop, you run a very good chance of nasty crashes. This | |
@@ -1478,16 +1486,62 b' class InteractiveShell(SingletonConfigurable, Magic):' | |||||
1478 | assert type(exc_tuple)==type(()) , \ |
|
1486 | assert type(exc_tuple)==type(()) , \ | |
1479 | "The custom exceptions must be given AS A TUPLE." |
|
1487 | "The custom exceptions must be given AS A TUPLE." | |
1480 |
|
1488 | |||
1481 | def dummy_handler(self,etype,value,tb): |
|
1489 | def dummy_handler(self,etype,value,tb,tb_offset=None): | |
1482 | print '*** Simple custom exception handler ***' |
|
1490 | print '*** Simple custom exception handler ***' | |
1483 | print 'Exception type :',etype |
|
1491 | print 'Exception type :',etype | |
1484 | print 'Exception value:',value |
|
1492 | print 'Exception value:',value | |
1485 | print 'Traceback :',tb |
|
1493 | print 'Traceback :',tb | |
1486 | #print 'Source code :','\n'.join(self.buffer) |
|
1494 | #print 'Source code :','\n'.join(self.buffer) | |
1487 |
|
1495 | |||
1488 | if handler is None: handler = dummy_handler |
|
1496 | def validate_stb(stb): | |
1489 |
|
1497 | """validate structured traceback return type | ||
1490 | self.CustomTB = types.MethodType(handler,self) |
|
1498 | ||
|
1499 | return type of CustomTB *should* be a list of strings, but allow | |||
|
1500 | single strings or None, which are harmless. | |||
|
1501 | ||||
|
1502 | This function will *always* return a list of strings, | |||
|
1503 | and will raise a TypeError if stb is inappropriate. | |||
|
1504 | """ | |||
|
1505 | msg = "CustomTB must return list of strings, not %r" % stb | |||
|
1506 | if stb is None: | |||
|
1507 | return [] | |||
|
1508 | elif isinstance(stb, basestring): | |||
|
1509 | return [stb] | |||
|
1510 | elif not isinstance(stb, list): | |||
|
1511 | raise TypeError(msg) | |||
|
1512 | # it's a list | |||
|
1513 | for line in stb: | |||
|
1514 | # check every element | |||
|
1515 | if not isinstance(line, basestring): | |||
|
1516 | raise TypeError(msg) | |||
|
1517 | return stb | |||
|
1518 | ||||
|
1519 | if handler is None: | |||
|
1520 | wrapped = dummy_handler | |||
|
1521 | else: | |||
|
1522 | def wrapped(self,etype,value,tb,tb_offset=None): | |||
|
1523 | """wrap CustomTB handler, to protect IPython from user code | |||
|
1524 | ||||
|
1525 | This makes it harder (but not impossible) for custom exception | |||
|
1526 | handlers to crash IPython. | |||
|
1527 | """ | |||
|
1528 | try: | |||
|
1529 | stb = handler(self,etype,value,tb,tb_offset=tb_offset) | |||
|
1530 | return validate_stb(stb) | |||
|
1531 | except: | |||
|
1532 | # clear custom handler immediately | |||
|
1533 | self.set_custom_exc((), None) | |||
|
1534 | print >> io.stderr, "Custom TB Handler failed, unregistering" | |||
|
1535 | # show the exception in handler first | |||
|
1536 | stb = self.InteractiveTB.structured_traceback(*sys.exc_info()) | |||
|
1537 | print >> io.stdout, self.InteractiveTB.stb2text(stb) | |||
|
1538 | print >> io.stdout, "The original exception:" | |||
|
1539 | stb = self.InteractiveTB.structured_traceback( | |||
|
1540 | (etype,value,tb), tb_offset=tb_offset | |||
|
1541 | ) | |||
|
1542 | return stb | |||
|
1543 | ||||
|
1544 | self.CustomTB = types.MethodType(wrapped,self) | |||
1491 | self.custom_exceptions = exc_tuple |
|
1545 | self.custom_exceptions = exc_tuple | |
1492 |
|
1546 | |||
1493 | def excepthook(self, etype, value, tb): |
|
1547 | def excepthook(self, etype, value, tb): | |
@@ -1556,11 +1610,7 b' class InteractiveShell(SingletonConfigurable, Magic):' | |||||
1556 | sys.last_value = value |
|
1610 | sys.last_value = value | |
1557 | sys.last_traceback = tb |
|
1611 | sys.last_traceback = tb | |
1558 | if etype in self.custom_exceptions: |
|
1612 | if etype in self.custom_exceptions: | |
1559 | # FIXME: Old custom traceback objects may just return a |
|
|||
1560 | # string, in that case we just put it into a list |
|
|||
1561 | stb = self.CustomTB(etype, value, tb, tb_offset) |
|
1613 | stb = self.CustomTB(etype, value, tb, tb_offset) | |
1562 | if isinstance(ctb, basestring): |
|
|||
1563 | stb = [stb] |
|
|||
1564 | else: |
|
1614 | else: | |
1565 | if exception_only: |
|
1615 | if exception_only: | |
1566 | stb = ['An exception has occurred, use %tb to see ' |
|
1616 | stb = ['An exception has occurred, use %tb to see ' | |
@@ -1571,9 +1621,11 b' class InteractiveShell(SingletonConfigurable, Magic):' | |||||
1571 | stb = self.InteractiveTB.structured_traceback(etype, |
|
1621 | stb = self.InteractiveTB.structured_traceback(etype, | |
1572 | value, tb, tb_offset=tb_offset) |
|
1622 | value, tb, tb_offset=tb_offset) | |
1573 |
|
1623 | |||
|
1624 | self._showtraceback(etype, value, stb) | |||
1574 | if self.call_pdb: |
|
1625 | if self.call_pdb: | |
1575 | # drop into debugger |
|
1626 | # drop into debugger | |
1576 | self.debugger(force=True) |
|
1627 | self.debugger(force=True) | |
|
1628 | return | |||
1577 |
|
1629 | |||
1578 | # Actually show the traceback |
|
1630 | # Actually show the traceback | |
1579 | self._showtraceback(etype, value, stb) |
|
1631 | self._showtraceback(etype, value, stb) |
@@ -50,6 +50,14 b" addflag('pdb', 'InteractiveShell.pdb'," | |||||
50 | "Enable auto calling the pdb debugger after every exception.", |
|
50 | "Enable auto calling the pdb debugger after every exception.", | |
51 | "Disable auto calling the pdb debugger after every exception." |
|
51 | "Disable auto calling the pdb debugger after every exception." | |
52 | ) |
|
52 | ) | |
|
53 | # pydb flag doesn't do any config, as core.debugger switches on import, | |||
|
54 | # which is before parsing. This just allows the flag to be passed. | |||
|
55 | shell_flags.update(dict( | |||
|
56 | pydb = ({}, | |||
|
57 | """"Use the third party 'pydb' package as debugger, instead of pdb. | |||
|
58 | Requires that pydb is installed.""" | |||
|
59 | ) | |||
|
60 | )) | |||
53 | addflag('pprint', 'PlainTextFormatter.pprint', |
|
61 | addflag('pprint', 'PlainTextFormatter.pprint', | |
54 | "Enable auto pretty printing of results.", |
|
62 | "Enable auto pretty printing of results.", | |
55 | "Disable auto auto pretty printing of results." |
|
63 | "Disable auto auto pretty printing of results." |
@@ -146,3 +146,37 b' class InteractiveShellTestCase(unittest.TestCase):' | |||||
146 | finally: |
|
146 | finally: | |
147 | # Reset compiler flags so we don't mess up other tests. |
|
147 | # Reset compiler flags so we don't mess up other tests. | |
148 | ip.compile.reset_compiler_flags() |
|
148 | ip.compile.reset_compiler_flags() | |
|
149 | ||||
|
150 | def test_bad_custom_tb(self): | |||
|
151 | """Check that InteractiveShell is protected from bad custom exception handlers""" | |||
|
152 | ip = get_ipython() | |||
|
153 | from IPython.utils import io | |||
|
154 | save_stderr = io.stderr | |||
|
155 | try: | |||
|
156 | # capture stderr | |||
|
157 | io.stderr = StringIO() | |||
|
158 | ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0) | |||
|
159 | self.assertEquals(ip.custom_exceptions, (IOError,)) | |||
|
160 | ip.run_cell(u'raise IOError("foo")') | |||
|
161 | self.assertEquals(ip.custom_exceptions, ()) | |||
|
162 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) | |||
|
163 | finally: | |||
|
164 | io.stderr = save_stderr | |||
|
165 | ||||
|
166 | def test_bad_custom_tb_return(self): | |||
|
167 | """Check that InteractiveShell is protected from bad return types in custom exception handlers""" | |||
|
168 | ip = get_ipython() | |||
|
169 | from IPython.utils import io | |||
|
170 | save_stderr = io.stderr | |||
|
171 | try: | |||
|
172 | # capture stderr | |||
|
173 | io.stderr = StringIO() | |||
|
174 | ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1) | |||
|
175 | self.assertEquals(ip.custom_exceptions, (NameError,)) | |||
|
176 | ip.run_cell(u'a=abracadabra') | |||
|
177 | self.assertEquals(ip.custom_exceptions, ()) | |||
|
178 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) | |||
|
179 | finally: | |||
|
180 | io.stderr = save_stderr | |||
|
181 | ||||
|
182 |
General Comments 0
You need to be logged in to leave comments.
Login now