Repository: ipython/ipython

Issue:
Incorrect call to `open` function not handled correctly.
<!-- This is the repository for IPython command line, if you can try to make sure this question/bug/feature belong here and not on one of the Jupyter repositories. 

If it's a generic Python/Jupyter question, try other forums or discourse.jupyter.org.

If you are unsure, it's ok to post here, though, there are few maintainer so you might not get a fast response. 

-->

If you run this code:

```
def crash(file_name="test.txt"): 
     open(file_name)                                                                                                                                                                                                             

crash(True)  
```

ipython will crash with the following error:

```
Traceback (most recent call last):
  File "/home/yunoac/anaconda3/bin/ipython", line 11, in <module>
    sys.exit(start_ipython())
  File "/home/yunoac/anaconda3/lib/python3.7/site-packages/IPython/__init__.py", line 125, in start_ipython
    return launch_new_instance(argv=argv, **kwargs)
  File "/home/yunoac/anaconda3/lib/python3.7/site-packages/traitlets/config/application.py", line 664, in launch_instance
    app.start()
  File "/home/yunoac/anaconda3/lib/python3.7/site-packages/IPython/terminal/ipapp.py", line 356, in start
    self.shell.mainloop()
  File "/home/yunoac/anaconda3/lib/python3.7/site-packages/IPython/terminal/interactiveshell.py", line 498, in mainloop
    self.interact()
  File "/home/yunoac/anaconda3/lib/python3.7/site-packages/IPython/terminal/interactiveshell.py", line 478, in interact
    print(self.separate_in, end='')
OSError: [Errno 9] Bad file descriptor

If you suspect this is an IPython bug, please report it at:
    https://github.com/ipython/ipython/issues
or send an email to the mailing list at ipython-dev@python.org

You can print a more detailed traceback right now with "%tb", or use "%debug"
to interactively debug it.

Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
    %config Application.verbose_crash=True

Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
OSError: [Errno 9] Bad file descriptor
```

Discussion:

Patch:
diff --git a/IPython/core/compilerop.py b/IPython/core/compilerop.py
index 228f70566..b43e570b3 100644
--- a/IPython/core/compilerop.py
+++ b/IPython/core/compilerop.py
@@ -73,6 +73,25 @@ class CachingCompiler(codeop.Compile):
     def __init__(self):
         codeop.Compile.__init__(self)
 
+        # This is ugly, but it must be done this way to allow multiple
+        # simultaneous ipython instances to coexist.  Since Python itself
+        # directly accesses the data structures in the linecache module, and
+        # the cache therein is global, we must work with that data structure.
+        # We must hold a reference to the original checkcache routine and call
+        # that in our own check_cache() below, but the special IPython cache
+        # must also be shared by all IPython instances.  If we were to hold
+        # separate caches (one in each CachingCompiler instance), any call made
+        # by Python itself to linecache.checkcache() would obliterate the
+        # cached data from the other IPython instances.
+        if not hasattr(linecache, '_ipython_cache'):
+            linecache._ipython_cache = {}
+        if not hasattr(linecache, '_checkcache_ori'):
+            linecache._checkcache_ori = linecache.checkcache
+        # Now, we must monkeypatch the linecache directly so that parts of the
+        # stdlib that call it outside our control go through our codepath
+        # (otherwise we'd lose our tracebacks).
+        linecache.checkcache = check_linecache_ipython
+
         # Caching a dictionary { filename: execution_count } for nicely
         # rendered tracebacks. The filename corresponds to the filename
         # argument used for the builtins.compile function.
@@ -142,24 +161,14 @@ def cache(self, transformed_code, number=0, raw_code=None):
         # Save the execution count
         self._filename_map[name] = number
 
-        # Since Python 2.5, setting mtime to `None` means the lines will
-        # never be removed by `linecache.checkcache`.  This means all the
-        # monkeypatching has *never* been necessary, since this code was
-        # only added in 2010, at which point IPython had already stopped
-        # supporting Python 2.4.
-        #
-        # Note that `linecache.clearcache` and `linecache.updatecache` may
-        # still remove our code from the cache, but those show explicit
-        # intent, and we should not try to interfere.  Normally the former
-        # is never called except when out of memory, and the latter is only
-        # called for lines *not* in the cache.
         entry = (
             len(transformed_code),
-            None,
+            time.time(),
             [line + "\n" for line in transformed_code.splitlines()],
             name,
         )
         linecache.cache[name] = entry
+        linecache._ipython_cache[name] = entry
         return name
 
     @contextmanager
@@ -178,22 +187,10 @@ def extra_flags(self, flags):
 
 
 def check_linecache_ipython(*args):
-    """Deprecated since IPython 8.6.  Call linecache.checkcache() directly.
-
-    It was already not necessary to call this function directly.  If no
-    CachingCompiler had been created, this function would fail badly.  If
-    an instance had been created, this function would've been monkeypatched
-    into place.
-
-    As of IPython 8.6, the monkeypatching has gone away entirely.  But there
-    were still internal callers of this function, so maybe external callers
-    also existed?
+    """Call linecache.checkcache() safely protecting our cached values.
     """
-    import warnings
-
-    warnings.warn(
-        "Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.",
-        DeprecationWarning,
-        stacklevel=2,
-    )
-    linecache.checkcache()
+    # First call the original checkcache as intended
+    linecache._checkcache_ori(*args)
+    # Then, update back the cache with our data, so that tracebacks related
+    # to our compiled codes can be produced.
+    linecache.cache.update(linecache._ipython_cache)
diff --git a/IPython/core/display.py b/IPython/core/display.py
index 23d8636b5..25b340fc8 100644
--- a/IPython/core/display.py
+++ b/IPython/core/display.py
@@ -627,7 +627,7 @@ def _repr_json_(self):
 
 
 _css_t = """var link = document.createElement("link");
-	link.rel = "stylesheet";
+	link.ref = "stylesheet";
 	link.type = "text/css";
 	link.href = "%s";
 	document.head.appendChild(link);
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
index c4eecff1f..214493b32 100644
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -61,7 +61,7 @@
 from IPython.core.alias import Alias, AliasManager
 from IPython.core.autocall import ExitAutocall
 from IPython.core.builtin_trap import BuiltinTrap
-from IPython.core.compilerop import CachingCompiler
+from IPython.core.compilerop import CachingCompiler, check_linecache_ipython
 from IPython.core.debugger import InterruptiblePdb
 from IPython.core.display_trap import DisplayTrap
 from IPython.core.displayhook import DisplayHook
@@ -270,6 +270,16 @@ def __repr__(self):
         return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s info=%s result=%s>' %\
                 (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.info), repr(self.result))
 
+@functools.wraps(io_open)
+def _modified_open(file, *args, **kwargs):
+    if file in {0, 1, 2}:
+        raise ValueError(
+            f"IPython won't let you open fd={file} by default "
+            "as it is likely to crash IPython. If you know what you are doing, "
+            "you can use builtins' open."
+        )
+
+    return io_open(file, *args, **kwargs)
 
 class InteractiveShell(SingletonConfigurable):
     """An enhanced, interactive shell for Python."""
@@ -1323,6 +1333,7 @@ def init_user_ns(self):
 
         ns['exit'] = self.exiter
         ns['quit'] = self.exiter
+        ns["open"] = _modified_open
 
         # Sync what we've added so far to user_ns_hidden so these aren't seen
         # by %who
@@ -1810,6 +1821,7 @@ def init_traceback_handlers(self, custom_exceptions):
         self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
                                                      color_scheme='NoColor',
                                                      tb_offset = 1,
+                                   check_cache=check_linecache_ipython,
                                    debugger_cls=self.debugger_cls, parent=self)
 
         # The instance will store a pointer to the system-wide exception hook,
diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py
index 7c4521829..68e100f7d 100644
--- a/IPython/core/pylabtools.py
+++ b/IPython/core/pylabtools.py
@@ -26,7 +26,6 @@
     "qt": "Qt5Agg",
     "osx": "MacOSX",
     "nbagg": "nbAgg",
-    "webagg": "WebAgg",
     "notebook": "nbAgg",
     "agg": "agg",
     "svg": "svg",
diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py
index e83e2b4a0..8f40c63b7 100644
--- a/IPython/core/ultratb.py
+++ b/IPython/core/ultratb.py
@@ -644,8 +644,10 @@ def __init__(
         self.long_header = long_header
         self.include_vars = include_vars
         # By default we use linecache.checkcache, but the user can provide a
-        # different check_cache implementation.  This was formerly used by the
-        # IPython kernel for interactive code, but is no longer necessary.
+        # different check_cache implementation.  This is used by the IPython
+        # kernel to provide tracebacks for interactive code that is cached,
+        # by a compiler instance that flushes the linecache but preserves its
+        # own code cache.
         if check_cache is None:
             check_cache = linecache.checkcache
         self.check_cache = check_cache
diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py
index c867b553f..cf2ab4277 100644
--- a/IPython/terminal/interactiveshell.py
+++ b/IPython/terminal/interactiveshell.py
@@ -711,8 +711,9 @@ def inputhook(self, context):
 
     active_eventloop = None
     def enable_gui(self, gui=None):
-        if gui and (gui not in {"inline", "webagg"}):
-            self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
+        if gui and (gui != 'inline') :
+            self.active_eventloop, self._inputhook =\
+                get_inputhook_name_and_func(gui)
         else:
             self.active_eventloop = self._inputhook = None
 


Test patch:
diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py
index 10827b5fa..982bd5a3b 100644
--- a/IPython/core/tests/test_interactiveshell.py
+++ b/IPython/core/tests/test_interactiveshell.py
@@ -103,6 +103,18 @@ def test_syntax_error(self):
         res = ip.run_cell("raise = 3")
         self.assertIsInstance(res.error_before_exec, SyntaxError)
 
+    def test_open_standard_input_stream(self):
+        res = ip.run_cell("open(0)")
+        self.assertIsInstance(res.error_in_exec, ValueError)
+
+    def test_open_standard_output_stream(self):
+        res = ip.run_cell("open(1)")
+        self.assertIsInstance(res.error_in_exec, ValueError)
+
+    def test_open_standard_error_stream(self):
+        res = ip.run_cell("open(2)")
+        self.assertIsInstance(res.error_in_exec, ValueError)
+
     def test_In_variable(self):
         "Verify that In variable grows with user input (GH-284)"
         oldlen = len(ip.user_ns['In'])