use module-bound exception

PiperOrigin-RevId: 811366325
diff --git a/python/_brotli.c b/python/_brotli.c
index 1f9150e..211cf83 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,33 @@
 "Implementation module for the Brotli library.");
 /* clang-format on */
 
-static void set_brotli_exception(const char* msg) {
+/*
+   Sets an exception with the given message.
+
+   `context` could be a module, a module type, or an instance of a module type.
+*/
+static void set_brotli_exception(PyObject* context, const char* msg) {
+#if PY_MAJOR_VERSION >= 3
+  PyObject* error = NULL;
+  assert(context != NULL);
+
+  if (PyModule_Check(context)) {
+    error = PyObject_GetAttrString(context, kErrorAttr);
+  } else {
+    PyObject* type =
+        PyType_Check(context) ? context : (PyObject*)PY_GET_TYPE(context);
+    PyObject* module = PyObject_GetAttrString(type, 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
 }
 
 /*
@@ -316,7 +346,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;
@@ -354,7 +384,7 @@
   self->processing = 0;
   self->enc = BrotliEncoderCreateInstance(0, 0, 0);
   if (self->enc == NULL) {
-    set_brotli_exception(kCompressCreateError);
+    set_brotli_exception((PyObject*)type, kCompressCreateError);
     PY_GET_TYPE(self)->tp_free((PyObject*)self);
     return NULL;
   }
@@ -386,7 +416,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((PyObject*)self, kInvalidModeError);
     self->healthy = 0;
     return -1;
   }
@@ -394,14 +424,14 @@
     BrotliEncoderSetParameter(self->enc, BROTLI_PARAM_QUALITY,
                               (uint32_t)quality);
   } else {
-    set_brotli_exception(kInvalidQualityError);
+    set_brotli_exception((PyObject*)self, 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((PyObject*)self, kInvalidLgwinError);
     self->healthy = 0;
     return -1;
   }
@@ -409,7 +439,7 @@
     BrotliEncoderSetParameter(self->enc, BROTLI_PARAM_LGBLOCK,
                               (uint32_t)lgblock);
   } else {
-    set_brotli_exception(kInvalidLgblockError);
+    set_brotli_exception((PyObject*)self, kInvalidLgblockError);
     self->healthy = 0;
     return -1;
   }
@@ -470,8 +500,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((PyObject*)self, kCompressError);
   }
 
 error:
@@ -495,11 +525,11 @@
   Py_buffer input;
 
   if (self->healthy == 0) {
-    set_brotli_exception(kCompressUnhealthyError);
+    set_brotli_exception((PyObject*)self, kCompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kCompressConcurrentError);
+    set_brotli_exception((PyObject*)self, kCompressConcurrentError);
     return NULL;
   }
 
@@ -522,11 +552,11 @@
   PyObject* ret = NULL;
 
   if (self->healthy == 0) {
-    set_brotli_exception(kCompressUnhealthyError);
+    set_brotli_exception((PyObject*)self, kCompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kCompressConcurrentError);
+    set_brotli_exception((PyObject*)self, kCompressConcurrentError);
     return NULL;
   }
 
@@ -540,11 +570,11 @@
   PyObject* ret = NULL;
 
   if (self->healthy == 0) {
-    set_brotli_exception(kCompressUnhealthyError);
+    set_brotli_exception((PyObject*)self, kCompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kCompressConcurrentError);
+    set_brotli_exception((PyObject*)self, kCompressConcurrentError);
     return NULL;
   }
 
@@ -578,7 +608,7 @@
 
   self->dec = BrotliDecoderCreateInstance(0, 0, 0);
   if (self->dec == NULL) {
-    set_brotli_exception(kDecompressCreateError);
+    set_brotli_exception((PyObject*)type, kDecompressCreateError);
     PY_GET_TYPE(self)->tp_free((PyObject*)self);
     return NULL;
   }
@@ -633,11 +663,11 @@
   int oom = 0;
 
   if (self->healthy == 0) {
-    set_brotli_exception(kDecompressUnhealthyError);
+    set_brotli_exception((PyObject*)self, kDecompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kDecompressConcurrentError);
+    set_brotli_exception((PyObject*)self, kDecompressConcurrentError);
     return NULL;
   }
 
@@ -654,7 +684,7 @@
 
   if (self->unconsumed_data_length > 0) {
     if (input.len > 0) {
-      set_brotli_exception(kDecompressSinkError);
+      set_brotli_exception((PyObject*)self, kDecompressSinkError);
       goto finally;
     }
     next_in = self->unconsumed_data;
@@ -693,7 +723,7 @@
   if (oom) {
     goto finally;
   } else if (result == BROTLI_DECODER_RESULT_ERROR) {
-    set_brotli_exception(kDecompressError);
+    set_brotli_exception((PyObject*)self, kDecompressError);
     goto finally;
   }
 
@@ -712,7 +742,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((PyObject*)self, kDecompressError);
     goto finally;
   }
 
@@ -742,11 +772,11 @@
 
 static PyObject* brotli_Decompressor_is_finished(PyBrotli_Decompressor* self) {
   if (self->healthy == 0) {
-    set_brotli_exception(kDecompressUnhealthyError);
+    set_brotli_exception((PyObject*)self, kDecompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kDecompressConcurrentError);
+    set_brotli_exception((PyObject*)self, kDecompressConcurrentError);
     return NULL;
   }
   if (BrotliDecoderIsFinished(self->dec)) {
@@ -759,11 +789,11 @@
 static PyObject* brotli_Decompressor_can_accept_more_data(
     PyBrotli_Decompressor* self) {
   if (self->healthy == 0) {
-    set_brotli_exception(kDecompressUnhealthyError);
+    set_brotli_exception((PyObject*)self, kDecompressUnhealthyError);
     return NULL;
   }
   if (self->processing != 0) {
-    set_brotli_exception(kDecompressConcurrentError);
+    set_brotli_exception((PyObject*)self, kDecompressConcurrentError);
     return NULL;
   }
   if (self->unconsumed_data_length > 0) {
@@ -832,7 +862,7 @@
   if (oom) {
     goto finally;
   } else if (result != BROTLI_DECODER_RESULT_SUCCESS || available_in > 0) {
-    set_brotli_exception(kDecompressError);
+    set_brotli_exception((PyObject*)self, kDecompressError);
     goto finally;
   }
 
@@ -1039,27 +1069,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;