| #include "Python.h" | 
 | #include "frameobject.h" | 
 |  | 
 | #include "pycore_pyerrors.h" | 
 |  | 
 | #define MAX_DISTANCE 3 | 
 | #define MAX_CANDIDATE_ITEMS 160 | 
 | #define MAX_STRING_SIZE 25 | 
 |  | 
 | /* Calculate the Levenshtein distance between string1 and string2 */ | 
 | static Py_ssize_t | 
 | levenshtein_distance(const char *a, size_t a_size, | 
 |                      const char *b, size_t b_size) { | 
 |  | 
 |     if (a_size > MAX_STRING_SIZE || b_size > MAX_STRING_SIZE) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     // Both strings are the same (by identity) | 
 |     if (a == b) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     // The first string is empty | 
 |     if (a_size == 0) { | 
 |         return b_size; | 
 |     } | 
 |  | 
 |     // The second string is empty | 
 |     if (b_size == 0) { | 
 |         return a_size; | 
 |     } | 
 |  | 
 |     size_t *buffer = PyMem_Calloc(a_size, sizeof(size_t)); | 
 |     if (buffer == NULL) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     // Initialize the buffer row | 
 |     size_t index = 0; | 
 |     while (index < a_size) { | 
 |         buffer[index] = index + 1; | 
 |         index++; | 
 |     } | 
 |  | 
 |     size_t b_index = 0; | 
 |     size_t result = 0; | 
 |     while (b_index < b_size) { | 
 |         char code = b[b_index]; | 
 |         size_t distance = result = b_index++; | 
 |         index = SIZE_MAX; | 
 |         while (++index < a_size) { | 
 |             size_t b_distance = code == a[index] ? distance : distance + 1; | 
 |             distance = buffer[index]; | 
 |             if (distance > result) { | 
 |                 if (b_distance > result) { | 
 |                     result = result + 1; | 
 |                 } else { | 
 |                     result = b_distance; | 
 |                 } | 
 |             } else { | 
 |                 if (b_distance > distance) { | 
 |                     result = distance + 1; | 
 |                 } else { | 
 |                     result = b_distance; | 
 |                 } | 
 |             } | 
 |             buffer[index] = result; | 
 |         } | 
 |     } | 
 |     PyMem_Free(buffer); | 
 |     return result; | 
 | } | 
 |  | 
 | static inline PyObject * | 
 | calculate_suggestions(PyObject *dir, | 
 |                       PyObject *name) { | 
 |     assert(!PyErr_Occurred()); | 
 |     assert(PyList_CheckExact(dir)); | 
 |  | 
 |     Py_ssize_t dir_size = PyList_GET_SIZE(dir); | 
 |     if (dir_size >= MAX_CANDIDATE_ITEMS) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     Py_ssize_t suggestion_distance = PyUnicode_GetLength(name); | 
 |     PyObject *suggestion = NULL; | 
 |     Py_ssize_t name_size; | 
 |     const char *name_str = PyUnicode_AsUTF8AndSize(name, &name_size); | 
 |     if (name_str == NULL) { | 
 |         return NULL; | 
 |     } | 
 |     for (int i = 0; i < dir_size; ++i) { | 
 |         PyObject *item = PyList_GET_ITEM(dir, i); | 
 |         Py_ssize_t item_size; | 
 |         const char *item_str = PyUnicode_AsUTF8AndSize(item, &item_size); | 
 |         if (item_str == NULL) { | 
 |             return NULL; | 
 |         } | 
 |         Py_ssize_t current_distance = levenshtein_distance( | 
 |                 name_str, name_size, item_str, item_size); | 
 |         if (current_distance == -1) { | 
 |             return NULL; | 
 |         } | 
 |         if (current_distance == 0 || | 
 |             current_distance > MAX_DISTANCE || | 
 |             current_distance * 2 > name_size) | 
 |         { | 
 |             continue; | 
 |         } | 
 |         if (!suggestion || current_distance < suggestion_distance) { | 
 |             suggestion = item; | 
 |             suggestion_distance = current_distance; | 
 |         } | 
 |     } | 
 |     if (!suggestion) { | 
 |         return NULL; | 
 |     } | 
 |     Py_INCREF(suggestion); | 
 |     return suggestion; | 
 | } | 
 |  | 
 | static PyObject * | 
 | offer_suggestions_for_attribute_error(PyAttributeErrorObject *exc) { | 
 |     PyObject *name = exc->name; // borrowed reference | 
 |     PyObject *obj = exc->obj; // borrowed reference | 
 |  | 
 |     // Abort if we don't have an attribute name or we have an invalid one | 
 |     if (name == NULL || obj == NULL || !PyUnicode_CheckExact(name)) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     PyObject *dir = PyObject_Dir(obj); | 
 |     if (dir == NULL) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     PyObject *suggestions = calculate_suggestions(dir, name); | 
 |     Py_DECREF(dir); | 
 |     return suggestions; | 
 | } | 
 |  | 
 |  | 
 | static PyObject * | 
 | offer_suggestions_for_name_error(PyNameErrorObject *exc) { | 
 |     PyObject *name = exc->name; // borrowed reference | 
 |     PyTracebackObject *traceback = (PyTracebackObject *) exc->traceback; // borrowed reference | 
 |     // Abort if we don't have a variable name or we have an invalid one | 
 |     // or if we don't have a traceback to work with | 
 |     if (name == NULL || traceback == NULL || !PyUnicode_CheckExact(name)) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     // Move to the traceback of the exception | 
 |     while (traceback->tb_next != NULL) { | 
 |         traceback = traceback->tb_next; | 
 |     } | 
 |  | 
 |     PyFrameObject *frame = traceback->tb_frame; | 
 |     assert(frame != NULL); | 
 |     PyCodeObject *code = frame->f_code; | 
 |     assert(code != NULL && code->co_varnames != NULL); | 
 |     PyObject *dir = PySequence_List(code->co_varnames); | 
 |     if (dir == NULL) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     PyObject *suggestions = calculate_suggestions(dir, name); | 
 |     Py_DECREF(dir); | 
 |     if (suggestions != NULL) { | 
 |         return suggestions; | 
 |     } | 
 |  | 
 |     dir = PySequence_List(frame->f_globals); | 
 |     if (dir == NULL) { | 
 |         return NULL; | 
 |     } | 
 |     suggestions = calculate_suggestions(dir, name); | 
 |     Py_DECREF(dir); | 
 |     if (suggestions != NULL) { | 
 |         return suggestions; | 
 |     } | 
 |  | 
 |     dir = PySequence_List(frame->f_builtins); | 
 |     if (dir == NULL) { | 
 |         return NULL; | 
 |     } | 
 |     suggestions = calculate_suggestions(dir, name); | 
 |     Py_DECREF(dir); | 
 |  | 
 |     return suggestions; | 
 | } | 
 |  | 
 | // Offer suggestions for a given exception. Returns a python string object containing the | 
 | // suggestions. This function returns NULL if no suggestion was found or if an exception happened, | 
 | // users must call PyErr_Occurred() to disambiguate. | 
 | PyObject *_Py_Offer_Suggestions(PyObject *exception) { | 
 |     PyObject *result = NULL; | 
 |     assert(!PyErr_Occurred()); | 
 |     if (Py_IS_TYPE(exception, (PyTypeObject*)PyExc_AttributeError)) { | 
 |         result = offer_suggestions_for_attribute_error((PyAttributeErrorObject *) exception); | 
 |     } else if (Py_IS_TYPE(exception, (PyTypeObject*)PyExc_NameError)) { | 
 |         result = offer_suggestions_for_name_error((PyNameErrorObject *) exception); | 
 |     } | 
 |     return result; | 
 | } | 
 |  |