use module-bound exception

PiperOrigin-RevId: 812739918
diff --git a/python/_brotli.c b/python/_brotli.c
index 1f9150e..0307347 100644
--- a/python/_brotli.c
+++ b/python/_brotli.c
@@ -1,6 +1,6 @@
 #include <assert.h>
-#include <stdio.h>
 #include <stddef.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #define PY_SSIZE_T_CLEAN 1
@@ -18,7 +18,12 @@
 #define PY_GET_TYPE(Obj) ((Obj)->ob_type)
 #endif
 
+static const char kErrorAttr[] = "error";
+#if PY_MAJOR_VERSION >= 3
+static const char kModuleAttr[] = "_module";
+#else
 static PyObject* BrotliError;
+#endif
 
 static const char kInvalidBufferError[] =
     "brotli: data must be a C-contiguous buffer";
@@ -195,8 +200,36 @@
 "Implementation module for the Brotli library.");
 /* clang-format on */
 
-static void set_brotli_exception(const char* msg) {
+static void set_brotli_exception(PyObject* t, const char* msg) {
+#if PY_MAJOR_VERSION >= 3
+  PyObject* error = NULL;
+  PyObject* module = NULL;
+  assert(t != NULL);
+  assert(PyType_Check(t));
+  module = PyObject_GetAttrString(t, kModuleAttr);
+  if (!module) return; /* AttributeError raised. */
+  error = PyObject_GetAttrString(module, kErrorAttr);
+  Py_DECREF(module);
+  if (error == NULL) return; /* AttributeError raised. */
+  PyErr_SetString(error, msg);
+  Py_DECREF(error);
+#else
   PyErr_SetString(BrotliError, msg);
+#endif
+}
+
+static void set_brotli_exception_from_module(PyObject* m, const char* msg) {
+#if PY_MAJOR_VERSION >= 3
+  PyObject* error = NULL;
+  assert(m != NULL);
+  assert(PyModule_Check(m));
+  error = PyObject_GetAttrString(m, kErrorAttr);
+  if (error == NULL) return; /* AttributeError raised. */
+  PyErr_SetString(error, msg);
+  Py_DECREF(error);
+#else
+  PyErr_SetString(BrotliError, msg);
+#endif
 }
 
 /*
@@ -316,7 +349,7 @@
 
   result = PyBytes_FromStringAndSize(NULL, len);
   if (result == NULL) {
-    PyErr_Clear();  /* OOM exception will be raised by callers. */
+    PyErr_Clear(); /* OOM exception will be raised by callers. */
     return NULL;
   }
   if (len == 0) return result;
@@ -347,6 +380,7 @@
                                        PyObject* keywds) {
   /* `tp_itemsize` is 0, so `nitems` could be 0. */
   PyBrotli_Compressor* self = (PyBrotli_Compressor*)type->tp_alloc(type, 0);
+  PyObject* self_type = (PyObject*)type;
 
   if (self == NULL) return NULL;
 
@@ -354,7 +388,7 @@
   self->processing = 0;
   self->enc = BrotliEncoderCreateInstance(0, 0, 0);
   if (self->enc == NULL) {
-    set_brotli_exception(kCompressCreateError);
+    set_brotli_exception(self_type, kCompressCreateError);
     PY_GET_TYPE(self)->tp_free((PyObject*)self);
     return NULL;
   }
@@ -367,6 +401,7 @@
                                   PyObject* keywds) {
   static const char* kwlist[] = {"mode", "quality", "lgwin", "lgblock", NULL};
 
+  PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self);
   unsigned char mode = BROTLI_DEFAULT_MODE;
   unsigned char quality = BROTLI_DEFAULT_QUALITY;
   unsigned char lgwin = BROTLI_DEFAULT_WINDOW;
@@ -386,7 +421,7 @@
   if ((mode == 0) || (mode == 1) || (mode == 2)) {
     BrotliEncoderSetParameter(self->enc, BROTLI_PARAM_MODE, (uint32_t)mode);
   } else {
-    set_brotli_exception(kInvalidModeError);
+    set_brotli_exception(self_type, kInvalidModeError);
     self->healthy = 0;
     return -1;
   }
@@ -394,14 +429,14 @@
     BrotliEncoderSetParameter(self->enc, BROTLI_PARAM_QUALITY,
                               (uint32_t)quality);
   } else {
-    set_brotli_exception(kInvalidQualityError);
+    set_brotli_exception(self_type, kInvalidQualityError);
     self->healthy = 0;
     return -1;
   }
   if ((10 <= lgwin) && (lgwin <= 24)) {
     BrotliEncoderSetParameter(self->enc, BROTLI_PARAM_LGWIN, (uint32_t)lgwin);
   } else {
-    set_brotli_exception(kInvalidLgwinError);
+    set_brotli_exception(self_type, kInvalidLgwinError);
     self->healthy = 0;
     return -1;
   }
@@ -409,7 +444,7 @@
     BrotliEncoderSetParameter(self->enc, BROTLI_PARAM_LGBLOCK,
                               (uint32_t)lgblock);
   } else {
-    set_brotli_exception(kInvalidLgblockError);
+    set_brotli_exception(self_type, kInvalidLgblockError);
     self->healthy = 0;
     return -1;
   }
@@ -431,6 +466,7 @@
 static PyObject* compress_stream(PyBrotli_Compressor* self,
                                  BrotliEncoderOperation op, uint8_t* input,
                                  size_t input_length) {
+  PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self);
   size_t available_in = input_length;
   const uint8_t* next_in = input;
   Buffer buffer;
@@ -470,8 +506,8 @@
   if (ok) {
     ret = Buffer_Finish(&buffer);
     if (ret == NULL) oom = 1;
-  } else {  /* Not ok */
-    set_brotli_exception(kCompressError);
+  } else { /* Not ok */
+    set_brotli_exception(self_type, kCompressError);
   }
 
 error:
@@ -490,16 +526,17 @@
 
 static PyObject* brotli_Compressor_process(PyBrotli_Compressor* self,
                                            PyObject* args) {
+  PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self);
   PyObject* ret = NULL;
   PyObject* input_object = NULL;
   Py_buffer input;
 
   if (self->healthy == 0) {
-    set_brotli_exception(kCompressUnhealthyError);
+    set_brotli_exception(self_type, kCompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kCompressConcurrentError);
+    set_brotli_exception(self_type, kCompressConcurrentError);
     return NULL;
   }
 
@@ -519,14 +556,15 @@
 }
 
 static PyObject* brotli_Compressor_flush(PyBrotli_Compressor* self) {
+  PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self);
   PyObject* ret = NULL;
 
   if (self->healthy == 0) {
-    set_brotli_exception(kCompressUnhealthyError);
+    set_brotli_exception(self_type, kCompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kCompressConcurrentError);
+    set_brotli_exception(self_type, kCompressConcurrentError);
     return NULL;
   }
 
@@ -537,14 +575,15 @@
 }
 
 static PyObject* brotli_Compressor_finish(PyBrotli_Compressor* self) {
+  PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self);
   PyObject* ret = NULL;
 
   if (self->healthy == 0) {
-    set_brotli_exception(kCompressUnhealthyError);
+    set_brotli_exception(self_type, kCompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kCompressConcurrentError);
+    set_brotli_exception(self_type, kCompressConcurrentError);
     return NULL;
   }
 
@@ -571,6 +610,7 @@
                                          PyObject* keywds) {
   /* `tp_itemsize` is 0, so `nitems` could be 0. */
   PyBrotli_Decompressor* self = (PyBrotli_Decompressor*)type->tp_alloc(type, 0);
+  PyObject* self_type = (PyObject*)type;
 
   if (self == NULL) return NULL;
   self->healthy = 0;
@@ -578,7 +618,7 @@
 
   self->dec = BrotliDecoderCreateInstance(0, 0, 0);
   if (self->dec == NULL) {
-    set_brotli_exception(kDecompressCreateError);
+    set_brotli_exception(self_type, kDecompressCreateError);
     PY_GET_TYPE(self)->tp_free((PyObject*)self);
     return NULL;
   }
@@ -620,6 +660,7 @@
                                              PyObject* args, PyObject* keywds) {
   static const char* kwlist[] = {"", "output_buffer_limit", NULL};
 
+  PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self);
   PyObject* ret = NULL;
   PyObject* input_object = NULL;
   Py_buffer input;
@@ -633,11 +674,11 @@
   int oom = 0;
 
   if (self->healthy == 0) {
-    set_brotli_exception(kDecompressUnhealthyError);
+    set_brotli_exception(self_type, kDecompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kDecompressConcurrentError);
+    set_brotli_exception(self_type, kDecompressConcurrentError);
     return NULL;
   }
 
@@ -654,7 +695,7 @@
 
   if (self->unconsumed_data_length > 0) {
     if (input.len > 0) {
-      set_brotli_exception(kDecompressSinkError);
+      set_brotli_exception(self_type, kDecompressSinkError);
       goto finally;
     }
     next_in = self->unconsumed_data;
@@ -693,7 +734,7 @@
   if (oom) {
     goto finally;
   } else if (result == BROTLI_DECODER_RESULT_ERROR) {
-    set_brotli_exception(kDecompressError);
+    set_brotli_exception(self_type, kDecompressError);
     goto finally;
   }
 
@@ -712,7 +753,7 @@
 
   if ((result == BROTLI_DECODER_RESULT_SUCCESS) && (avail_in > 0)) {
     /* TODO(eustas): Add API to ignore / fetch unused "tail"? */
-    set_brotli_exception(kDecompressError);
+    set_brotli_exception(self_type, kDecompressError);
     goto finally;
   }
 
@@ -741,12 +782,13 @@
 }
 
 static PyObject* brotli_Decompressor_is_finished(PyBrotli_Decompressor* self) {
+  PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self);
   if (self->healthy == 0) {
-    set_brotli_exception(kDecompressUnhealthyError);
+    set_brotli_exception(self_type, kDecompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kDecompressConcurrentError);
+    set_brotli_exception(self_type, kDecompressConcurrentError);
     return NULL;
   }
   if (BrotliDecoderIsFinished(self->dec)) {
@@ -758,12 +800,13 @@
 
 static PyObject* brotli_Decompressor_can_accept_more_data(
     PyBrotli_Decompressor* self) {
+  PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self);
   if (self->healthy == 0) {
-    set_brotli_exception(kDecompressUnhealthyError);
+    set_brotli_exception(self_type, kDecompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kDecompressConcurrentError);
+    set_brotli_exception(self_type, kDecompressConcurrentError);
     return NULL;
   }
   if (self->unconsumed_data_length > 0) {
@@ -775,7 +818,7 @@
 
 /* --- Module functions --- */
 
-static PyObject* brotli_decompress(PyObject* self, PyObject* args,
+static PyObject* brotli_decompress(PyObject* m, PyObject* args,
                                    PyObject* keywds) {
   static const char* kwlist[] = {"string", NULL};
 
@@ -832,7 +875,7 @@
   if (oom) {
     goto finally;
   } else if (result != BROTLI_DECODER_RESULT_SUCCESS || available_in > 0) {
-    set_brotli_exception(kDecompressError);
+    set_brotli_exception_from_module(m, kDecompressError);
     goto finally;
   }
 
@@ -1039,27 +1082,35 @@
                                          brotli_error_doc, NULL, NULL);
   if (error_type == NULL) goto error;
 
-  if (RegisterObject(m, "error", error_type) < 0) goto error;
+  if (RegisterObject(m, kErrorAttr, error_type) < 0) goto error;
+#if PY_MAJOR_VERSION < 3
   /* Assumption: pointer is used only while module is alive and well. */
   BrotliError = error_type;
+#endif
   error_type = NULL;
 
 #if PY_MAJOR_VERSION >= 3
   compressor_type = PyType_FromSpec(&brotli_Compressor_spec);
   decompressor_type = PyType_FromSpec(&brotli_Decompressor_spec);
 #else
-  compressor_type = &brotli_CompressorType;
+  compressor_type = (PyObject*)&brotli_CompressorType;
   Py_INCREF(compressor_type);
-  decompressor_type = &brotli_DecompressorType;
+  decompressor_type = (PyObject*)&brotli_DecompressorType;
   Py_INCREF(decompressor_type);
 #endif
   if (compressor_type == NULL) goto error;
   if (PyType_Ready((PyTypeObject*)compressor_type) < 0) goto error;
+#if PY_MAJOR_VERSION >= 3
+  if (PyObject_SetAttrString(compressor_type, kModuleAttr, m) < 0) goto error;
+#endif
   if (RegisterObject(m, "Compressor", compressor_type) < 0) goto error;
   compressor_type = NULL;
 
   if (decompressor_type == NULL) goto error;
   if (PyType_Ready((PyTypeObject*)decompressor_type) < 0) goto error;
+#if PY_MAJOR_VERSION >= 3
+  if (PyObject_SetAttrString(decompressor_type, kModuleAttr, m) < 0) goto error;
+#endif
   if (RegisterObject(m, "Decompressor", decompressor_type) < 0) goto error;
   decompressor_type = NULL;