// Observability shim: GTK/GLib are system libraries built without ASAN
// instrumentation, so when Chrome calls g_object_set_data() on the freed
// GtkFileChooserDialog, the read of ((GTypeInstance*)dialog)->g_class inside
// libgobject doesn't consult ASAN shadow memory and ASAN stays silent (GLib
// just prints "assertion 'G_IS_OBJECT (object)' failed").
//
// This shim wraps the GLib type-check entry points and probes the first
// pointer-sized field of the instance via memcpy(), which *is* intercepted by
// ASAN.  It performs exactly the same read the real function performs, so it
// introduces no new behaviour — it only makes the existing read visible to
// ASAN.
//
// Build:
//   cc -shared -fPIC -O1 -o asan_gobject_probe.so asan_gobject_probe.c -ldl

#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>

static void probe(const void* p) {
  if (!p) return;
  void* tmp;
  // ASAN intercepts memcpy and checks shadow memory for the source range.
  memcpy(&tmp, p, sizeof(tmp));
  // Prevent the compiler from eliding the read.
  __asm__ __volatile__("" :: "r"(tmp) : "memory");
}

void* g_type_check_instance_cast(void* inst, unsigned long type) {
  static void* (*real)(void*, unsigned long);
  if (!real) real = dlsym(RTLD_NEXT, "g_type_check_instance_cast");
  probe(inst);
  return real(inst, type);
}

int g_type_check_instance_is_fundamentally_a(void* inst, unsigned long type) {
  static int (*real)(void*, unsigned long);
  if (!real) real = dlsym(RTLD_NEXT, "g_type_check_instance_is_fundamentally_a");
  probe(inst);
  return real(inst, type);
}

void g_object_set_data(void* obj, const char* key, void* data) {
  static void (*real)(void*, const char*, void*);
  if (!real) real = dlsym(RTLD_NEXT, "g_object_set_data");
  probe(obj);
  real(obj, key, data);
}
