##// END OF EJS Templates
DEV: AST transformers supplied to `InteractiveShell` can reject input....
Scott Sanderson -
Show More
@@ -54,6 +54,7 b' from IPython.core.profiledir import ProfileDir'
54 54 from IPython.core.prompts import PromptManager
55 55 from IPython.core.usage import default_banner
56 56 from IPython.lib.latextools import LaTeXTool
57 from IPython.lib.security import InputRejected
57 58 from IPython.testing.skipdoctest import skip_doctest
58 59 from IPython.utils import PyColorize
59 60 from IPython.utils import io
@@ -2786,7 +2787,13 b' class InteractiveShell(SingletonConfigurable):'
2786 2787 return None
2787 2788
2788 2789 # Apply AST transformations
2789 code_ast = self.transform_ast(code_ast)
2790 try:
2791 code_ast = self.transform_ast(code_ast)
2792 except InputRejected:
2793 self.showtraceback()
2794 if store_history:
2795 self.execution_count += 1
2796 return None
2790 2797
2791 2798 # Execute the user code
2792 2799 interactivity = "none" if silent else self.ast_node_interactivity
@@ -2822,6 +2829,11 b' class InteractiveShell(SingletonConfigurable):'
2822 2829 for transformer in self.ast_transformers:
2823 2830 try:
2824 2831 node = transformer.visit(node)
2832 except InputRejected:
2833 # User-supplied AST transformers can reject an input by raising
2834 # an InputRejected. Short-circuit in this case so that we
2835 # don't unregister the transform.
2836 raise
2825 2837 except Exception:
2826 2838 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
2827 2839 self.ast_transformers.remove(transformer)
@@ -25,6 +25,7 b' from os.path import join'
25 25 import nose.tools as nt
26 26
27 27 from IPython.core.inputtransformer import InputTransformer
28 from IPython.lib.security import InputRejected
28 29 from IPython.testing.decorators import (
29 30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 31 )
@@ -695,6 +696,41 b' class TestAstTransformError(unittest.TestCase):'
695 696 # This should have been removed.
696 697 nt.assert_not_in(err_transformer, ip.ast_transformers)
697 698
699
700 class StringRejector(ast.NodeTransformer):
701 """Throws an InputRejected when it sees a string literal.
702
703 Used to verify that NodeTransformers can signal that a piece of code should
704 not be executed by throwing an InputRejected.
705 """
706
707 def visit_Str(self, node):
708 raise InputRejected("test")
709
710
711 class TestAstTransformInputRejection(unittest.TestCase):
712
713 def setUp(self):
714 self.transformer = StringRejector()
715 ip.ast_transformers.append(self.transformer)
716
717 def tearDown(self):
718 ip.ast_transformers.remove(self.transformer)
719
720 def test_input_rejection(self):
721 """Check that NodeTransformers can reject input."""
722
723 expect_exception_tb = tt.AssertPrints("InputRejected: test")
724 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
725
726 # Run the same check twice to verify that the transformer is not
727 # disabled after raising.
728 with expect_exception_tb, expect_no_cell_output:
729 ip.run_cell("'unsafe'")
730
731 with expect_exception_tb, expect_no_cell_output:
732 ip.run_cell("'unsafe'")
733
698 734 def test__IPYTHON__():
699 735 # This shouldn't raise a NameError, that's all
700 736 __IPYTHON__
@@ -114,3 +114,13 b' def passwd_check(hashed_passphrase, passphrase):'
114 114 h.update(cast_bytes(passphrase, 'utf-8') + cast_bytes(salt, 'ascii'))
115 115
116 116 return h.hexdigest() == pw_digest
117
118 #-----------------------------------------------------------------------------
119 # Exception classes
120 #-----------------------------------------------------------------------------
121 class InputRejected(Exception):
122 """Input rejected by ast transformer.
123
124 To be raised by user-supplied ast.NodeTransformers when an input should not
125 be executed.
126 """
General Comments 0
You need to be logged in to leave comments. Login now