Repository: ipython/ipython

Issue:
`OSError: could not get source code` while handling another error
Repros with IPython 8.12.1+ but not 8.12.0:

```python
import pandas
df = pandas.DataFrame([])

# Important: only fails inside of an "exec" call:
exec("df.foobarbaz()")
```

Result:
```
Unexpected exception formatting exception. Falling back to standard exception
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3508, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-4-8739193a1fca>", line 1, in <module>
    exec("df.foobarbaz()")
  File "<string>", line 1, in <module>
  File "/usr/local/lib/python3.10/site-packages/pandas/core/generic.py", line 5989, in __getattr__
    return object.__getattribute__(self, name)
AttributeError: 'DataFrame' object has no attribute 'foobarbaz'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 2105, in showtraceback
    stb = self.InteractiveTB.structured_traceback(
  File "/usr/local/lib/python3.10/site-packages/IPython/core/ultratb.py", line 1396, in structured_traceback
    return FormattedTB.structured_traceback(
  File "/usr/local/lib/python3.10/site-packages/IPython/core/ultratb.py", line 1287, in structured_traceback
    return VerboseTB.structured_traceback(
  File "/usr/local/lib/python3.10/site-packages/IPython/core/ultratb.py", line 1140, in structured_traceback
    formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
  File "/usr/local/lib/python3.10/site-packages/IPython/core/ultratb.py", line 1030, in format_exception_as_a_whole
    self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
  File "/usr/local/lib/python3.10/site-packages/IPython/core/ultratb.py", line 1122, in get_records
    FrameInfo(
  File "/usr/local/lib/python3.10/site-packages/IPython/core/ultratb.py", line 766, in __init__
    ix = inspect.getsourcelines(frame)
  File "/usr/local/lib/python3.10/inspect.py", line 1121, in getsourcelines
    lines, lnum = findsource(object)
  File "/usr/local/lib/python3.10/inspect.py", line 958, in findsource
    raise OSError('could not get source code')
OSError: could not get source code
```



Discussion:
I've just come across this https://github.com/sherpa/sherpa which has a lot of user-level code that - for historical reasons - uses a lot of exec calls behind the scenes. So instead of a simple TypeError about an invalid option, they would get the following: 

```
In [4]: from sherpa import ui

In [5]: ui.set_iter_method("siggy")
Unexpected exception formatting exception. Falling back to standard exception
Traceback (most recent call last):
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3508, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-5-336ae7d1c68f>", line 1, in <module>
    ui.set_iter_method("siggy")
  File "<string>", line 1, in set_iter_method
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/sherpa/ui/utils.py", line 2526, in set_iter_method
    raise TypeError(f'{meth} is not an iterative fitting method')
TypeError: siggy is not an iterative fitting method

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 2105, in showtraceback
    stb = self.InteractiveTB.structured_traceback(
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/IPython/core/ultratb.py", line 1396, in structured_traceback
    return FormattedTB.structured_traceback(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/IPython/core/ultratb.py", line 1287, in structured_traceback
    return VerboseTB.structured_traceback(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/IPython/core/ultratb.py", line 1140, in structured_traceback
    formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/IPython/core/ultratb.py", line 1030, in format_exception_as_a_whole
    self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/IPython/core/ultratb.py", line 1122, in get_records
    FrameInfo(
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/site-packages/IPython/core/ultratb.py", line 766, in __init__
    ix = inspect.getsourcelines(frame)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/inspect.py", line 1244, in getsourcelines
    lines, lnum = findsource(object)
                  ^^^^^^^^^^^^^^^^^^
  File "/home/dburke/miniconda3/envs/delme/lib/python3.11/inspect.py", line 1081, in findsource
    raise OSError('could not get source code')
OSError: could not get source code
```


I've discovered a workaround for the issue when invoking `exec` directly, which is to first register the code with the shell compiler:

```python
import pandas
from IPython import get_ipython

df = pandas.DataFrame([])

code = "df.foobarbaz()"
name = get_ipython().compile.cache(code)
exec(compile(code, name, "exec"))
```

Though naturally this doesn't help when working with a library that uses `exec` calls internally (such as above).

Resolving patch:
diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py
--- a/IPython/core/ultratb.py
+++ b/IPython/core/ultratb.py
@@ -743,6 +743,7 @@ class FrameInfo:
     lineno: Tuple[int]
     # number of context lines to use
     context: Optional[int]
+    raw_lines: List[str]
 
     @classmethod
     def _from_stack_data_FrameInfo(cls, frame_info):
@@ -777,8 +778,13 @@ def __init__(
 
         # self.lines = []
         if sd is None:
-            ix = inspect.getsourcelines(frame)
-            self.raw_lines = ix[0]
+            try:
+                # return a list of source lines and a starting line number
+                self.raw_lines = inspect.getsourcelines(frame)[0]
+            except OSError:
+                self.raw_lines = [
+                    "'Could not get source, probably due dynamically evaluated source code.'"
+                ]
 
     @property
     def variables_in_executing_piece(self):


Test patch:
diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py
--- a/IPython/core/tests/test_ultratb.py
+++ b/IPython/core/tests/test_ultratb.py
@@ -14,7 +14,7 @@
 
 from IPython.core.ultratb import ColorTB, VerboseTB
 from IPython.testing import tools as tt
-from IPython.testing.decorators import onlyif_unicode_paths
+from IPython.testing.decorators import onlyif_unicode_paths, skip_without
 from IPython.utils.syspathcontext import prepended_to_syspath
 
 file_1 = """1
@@ -177,6 +177,20 @@ def test_indentationerror_shows_line(self):
                 with tt.AssertPrints("zoon()", suppress=False):
                     ip.magic('run %s' % fname)
 
+@skip_without("pandas")
+def test_dynamic_code():
+    code = """
+    import pandas
+    df = pandas.DataFrame([])
+
+    # Important: only fails inside of an "exec" call:
+    exec("df.foobarbaz()")
+    """
+
+    with tt.AssertPrints("Could not get source"):
+        ip.run_cell(code)
+
+
 se_file_1 = """1
 2
 7/