Skip to content

Commit 6a91132

Browse files
pablogsalDinoV
authored andcommitted
Implement better error
1 parent f992ee7 commit 6a91132

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

Include/internal/pycore_lazyimportobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ typedef struct {
1717
PyObject *lz_import_func;
1818
PyObject *lz_from;
1919
PyObject *lz_attr;
20+
/* Frame information for the original import location */
21+
PyCodeObject *lz_code; /* code object where the lazy import was created */
22+
int lz_instr_offset; /* instruction offset where the lazy import was created */
2023
} PyLazyImportObject;
2124

2225

Objects/lazyimportobject.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#include "Python.h"
44
#include "pycore_import.h"
55
#include "pycore_lazyimportobject.h"
6+
#include "pycore_frame.h"
7+
#include "pycore_ceval.h"
8+
#include "pycore_interpframe.h"
69

710
PyObject *
811
_PyLazyImport_New(PyObject *import_func, PyObject *from, PyObject *attr)
@@ -26,6 +29,21 @@ _PyLazyImport_New(PyObject *import_func, PyObject *from, PyObject *attr)
2629
m->lz_from = from;
2730
Py_XINCREF(attr);
2831
m->lz_attr = attr;
32+
33+
/* Capture frame information for the original import location */
34+
m->lz_code = NULL;
35+
m->lz_instr_offset = -1;
36+
37+
_PyInterpreterFrame *frame = _PyEval_GetFrame();
38+
if (frame != NULL) {
39+
PyCodeObject *code = _PyFrame_GetCode(frame);
40+
if (code != NULL) {
41+
m->lz_code = (PyCodeObject *)Py_NewRef(code);
42+
/* Calculate the instruction offset from the current frame */
43+
m->lz_instr_offset = _PyInterpreterFrame_LASTI(frame);
44+
}
45+
}
46+
2947
PyObject_GC_Track(m);
3048
return (PyObject *)m;
3149
}
@@ -37,6 +55,7 @@ lazy_import_dealloc(PyLazyImportObject *m)
3755
Py_XDECREF(m->lz_import_func);
3856
Py_XDECREF(m->lz_from);
3957
Py_XDECREF(m->lz_attr);
58+
Py_XDECREF(m->lz_code);
4059
Py_TYPE(m)->tp_free((PyObject *)m);
4160
}
4261

@@ -72,6 +91,7 @@ lazy_import_traverse(PyLazyImportObject *m, visitproc visit, void *arg)
7291
Py_VISIT(m->lz_import_func);
7392
Py_VISIT(m->lz_from);
7493
Py_VISIT(m->lz_attr);
94+
Py_VISIT(m->lz_code);
7595
return 0;
7696
}
7797

@@ -81,6 +101,7 @@ lazy_import_clear(PyLazyImportObject *m)
81101
Py_CLEAR(m->lz_import_func);
82102
Py_CLEAR(m->lz_from);
83103
Py_CLEAR(m->lz_attr);
104+
Py_CLEAR(m->lz_code);
84105
return 0;
85106
}
86107

Python/import.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "pycore_interp.h" // struct _import_runtime_state
1010
#include "pycore_long.h" // _PyLong_GetZero
1111
#include "pycore_lazyimportobject.h"
12+
#include "pycore_traceback.h"
13+
#include "pycore_interpframe.h"
1214
#include "pycore_magic_number.h" // PYC_MAGIC_NUMBER_TOKEN
1315
#include "pycore_moduleobject.h" // _PyModule_GetDef()
1416
#include "pycore_namespace.h" // _PyNamespace_Type
@@ -3780,6 +3782,75 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
37803782
Py_XDECREF(obj);
37813783
obj = NULL;
37823784

3785+
/* If an error occurred and we have frame information, add it to the exception */
3786+
if (PyErr_Occurred() && lz->lz_code != NULL && lz->lz_instr_offset >= 0) {
3787+
/* Get the current exception - this already has the full traceback from the access point */
3788+
PyObject *exc = _PyErr_GetRaisedException(tstate);
3789+
3790+
/* Get import name - this can fail and set an exception */
3791+
PyObject *import_name = _PyLazyImport_GetName(lazy_import);
3792+
if (!import_name) {
3793+
/* Failed to get import name, just restore original exception */
3794+
_PyErr_SetRaisedException(tstate, exc);
3795+
goto ok;
3796+
}
3797+
3798+
/* Resolve line number from instruction offset on demand */
3799+
int lineno = PyCode_Addr2Line((PyCodeObject *)lz->lz_code, lz->lz_instr_offset);
3800+
3801+
/* Get strings - these can return NULL on encoding errors */
3802+
const char *filename_str = PyUnicode_AsUTF8(lz->lz_code->co_filename);
3803+
if (!filename_str) {
3804+
/* Unicode conversion failed - clear error and restore original exception */
3805+
PyErr_Clear();
3806+
Py_DECREF(import_name);
3807+
_PyErr_SetRaisedException(tstate, exc);
3808+
goto ok;
3809+
}
3810+
3811+
const char *funcname_str = PyUnicode_AsUTF8(lz->lz_code->co_name);
3812+
if (!funcname_str) {
3813+
/* Unicode conversion failed - clear error and restore original exception */
3814+
PyErr_Clear();
3815+
Py_DECREF(import_name);
3816+
_PyErr_SetRaisedException(tstate, exc);
3817+
goto ok;
3818+
}
3819+
3820+
/* Create a cause exception showing where the lazy import was declared */
3821+
PyObject *msg = PyUnicode_FromFormat(
3822+
"deferred import of '%U' raised an exception during resolution",
3823+
import_name
3824+
);
3825+
Py_DECREF(import_name); /* Done with import_name regardless of what happens next */
3826+
3827+
if (!msg) {
3828+
/* Failed to create message - restore original exception */
3829+
_PyErr_SetRaisedException(tstate, exc);
3830+
goto ok;
3831+
}
3832+
3833+
PyObject *cause_exc = PyObject_CallOneArg(PyExc_ImportError, msg);
3834+
Py_DECREF(msg); /* Done with msg */
3835+
3836+
if (!cause_exc) {
3837+
/* Failed to create exception - restore original */
3838+
_PyErr_SetRaisedException(tstate, exc);
3839+
goto ok;
3840+
}
3841+
3842+
/* Add traceback entry for the lazy import declaration */
3843+
_PyErr_SetRaisedException(tstate, cause_exc);
3844+
_PyTraceback_Add(funcname_str, filename_str, lineno);
3845+
PyObject *cause_with_tb = _PyErr_GetRaisedException(tstate);
3846+
3847+
/* Set the cause on the original exception */
3848+
PyException_SetCause(exc, cause_with_tb); /* Steals ref to cause_with_tb */
3849+
3850+
/* Restore the original exception with its full traceback */
3851+
_PyErr_SetRaisedException(tstate, exc);
3852+
}
3853+
37833854
ok:
37843855
Py_XDECREF(fromlist);
37853856
return obj;

0 commit comments

Comments
 (0)