+#include <assert.h>
+// Always run a, then assert it is true:
+#define DO_ASSERT(a) do { bool _assert_val = (a); assert(_assert_val); } while(0)
+// Assert a is true or do nothing
+#define CHECK(a) DO_ASSERT(a)
+
+void __attribute__((constructor)) debug_log_version() {
+ if (check_get_ldk_version() == NULL)
+ DEBUG_PRINT("LDK version did not match the header we built against\n");
+ if (check_get_ldk_bindings_version() == NULL)
+ DEBUG_PRINT("LDK C Bindings version did not match the header we built against\n");
+ DEBUG_PRINT("Loaded LDK-Java Bindings with LDK %s and LDK-C-Bindings %s\n", check_get_ldk_version(), check_get_ldk_bindings_version());
+}
+
+// Running a leak check across all the allocations and frees of the JDK is a mess,
+// so instead we implement our own naive leak checker here, relying on the -wrap
+// linker option to wrap malloc/calloc/realloc/free, tracking everyhing allocated
+// and free'd in Rust or C across the generated bindings shared library.
+#include <threads.h>
+#include <execinfo.h>
+
+#include <unistd.h>
+static mtx_t allocation_mtx;
+
+void __attribute__((constructor)) init_mtx() {
+ DO_ASSERT(mtx_init(&allocation_mtx, mtx_plain) == thrd_success);
+}
+
+#define BT_MAX 128
+typedef struct allocation {
+ struct allocation* next;
+ void* ptr;
+ const char* struct_name;
+ void* bt[BT_MAX];
+ int bt_len;
+ unsigned long alloc_len;
+} allocation;
+static allocation* allocation_ll = NULL;
+
+void* __real_malloc(size_t len);
+void* __real_calloc(size_t nmemb, size_t len);
+static void new_allocation(void* res, const char* struct_name, size_t len) {
+ allocation* new_alloc = __real_malloc(sizeof(allocation));
+ new_alloc->ptr = res;
+ new_alloc->struct_name = struct_name;
+ new_alloc->bt_len = backtrace(new_alloc->bt, BT_MAX);
+ new_alloc->alloc_len = len;
+ DO_ASSERT(mtx_lock(&allocation_mtx) == thrd_success);
+ new_alloc->next = allocation_ll;
+ allocation_ll = new_alloc;
+ DO_ASSERT(mtx_unlock(&allocation_mtx) == thrd_success);
+}
+static void* MALLOC(size_t len, const char* struct_name) {
+ void* res = __real_malloc(len);
+ new_allocation(res, struct_name, len);
+ return res;
+}
+void __real_free(void* ptr);
+static void alloc_freed(void* ptr) {
+ allocation* p = NULL;
+ DO_ASSERT(mtx_lock(&allocation_mtx) == thrd_success);
+ allocation* it = allocation_ll;
+ while (it->ptr != ptr) {
+ p = it; it = it->next;
+ if (it == NULL) {
+ DEBUG_PRINT("Tried to free unknown pointer %p at:\n", ptr);
+ void* bt[BT_MAX];
+ int bt_len = backtrace(bt, BT_MAX);
+ backtrace_symbols_fd(bt, bt_len, STDERR_FILENO);
+ DEBUG_PRINT("\n\n");
+ DO_ASSERT(mtx_unlock(&allocation_mtx) == thrd_success);
+ return; // addrsan should catch malloc-unknown and print more info than we have
+ }
+ }
+ if (p) { p->next = it->next; } else { allocation_ll = it->next; }
+ DO_ASSERT(mtx_unlock(&allocation_mtx) == thrd_success);
+ DO_ASSERT(it->ptr == ptr);
+ __real_free(it);
+}
+static void FREE(void* ptr) {
+ if ((uint64_t)ptr < 1024) return; // Rust loves to create pointers to the NULL page for dummys
+ alloc_freed(ptr);
+ __real_free(ptr);
+}
+
+void* __wrap_malloc(size_t len) {
+ void* res = __real_malloc(len);
+ new_allocation(res, "malloc call", len);
+ return res;
+}
+void* __wrap_calloc(size_t nmemb, size_t len) {
+ void* res = __real_calloc(nmemb, len);
+ new_allocation(res, "calloc call", len);
+ return res;
+}
+void __wrap_free(void* ptr) {
+ if (ptr == NULL) return;
+ alloc_freed(ptr);
+ __real_free(ptr);
+}
+
+void* __real_realloc(void* ptr, size_t newlen);
+void* __wrap_realloc(void* ptr, size_t len) {
+ if (ptr != NULL) alloc_freed(ptr);
+ void* res = __real_realloc(ptr, len);
+ new_allocation(res, "realloc call", len);
+ return res;
+}
+void __wrap_reallocarray(void* ptr, size_t new_sz) {
+ // Rust doesn't seem to use reallocarray currently
+ DO_ASSERT(false);
+}
+
+void __attribute__((destructor)) check_leaks() {
+ unsigned long alloc_count = 0;
+ unsigned long alloc_size = 0;
+ DEBUG_PRINT("The following LDK-allocated blocks still remain.\n");
+ DEBUG_PRINT("Note that this is only accurate if System.gc(); System.runFinalization()\n");
+ DEBUG_PRINT("was called prior to exit after all LDK objects were out of scope.\n");
+ for (allocation* a = allocation_ll; a != NULL; a = a->next) {
+ DEBUG_PRINT("%s %p (%lu bytes) remains:\n", a->struct_name, a->ptr, a->alloc_len);
+ backtrace_symbols_fd(a->bt, a->bt_len, STDERR_FILENO);
+ DEBUG_PRINT("\n\n");
+ alloc_count++;
+ alloc_size += a->alloc_len;
+ }
+ DEBUG_PRINT("%lu allocations remained for %lu bytes.\n", alloc_count, alloc_size);
+ DEBUG_PRINT("Note that this is only accurate if System.gc(); System.runFinalization()\n");
+ DEBUG_PRINT("was called prior to exit after all LDK objects were out of scope.\n");
+}