System.loadLibrary(\"lightningjni\");
init(java.lang.Enum.class, VecOrSliceDef.class);
init_class_cache();
+ if (!get_lib_version_string().equals(get_ldk_java_bindings_version()))
+ throw new IllegalArgumentException("Compiled LDK library and LDK class failes do not match");
+ // Fetching the LDK versions from C also checks that the header and binaries match
+ get_ldk_c_bindings_version();
+ get_ldk_version();
}
static native void init(java.lang.Class c, java.lang.Class slicedef);
static native void init_class_cache();
+ static native String get_lib_version_string();
+
+ public static String get_ldk_java_bindings_version() {
+ return "<git_version_ldk_garbagecollected>";
+ }
+ public static native String get_ldk_c_bindings_version();
+ public static native String get_ldk_version();
public static native boolean deref_bool(long ptr);
public static native long deref_long(long ptr);
self.util_fn_pfx = """package org.ldk.structs;
import org.ldk.impl.bindings;
import java.util.Arrays;
+import org.ldk.enums.*;
public class UtilMethods {
"""
"""
self.c_file_pfx = """#include \"org_ldk_impl_bindings.h\"
-#include <rust_types.h>
#include <lightning.h>
#include <string.h>
#include <stdatomic.h>
"""
+ if self.target == Target.ANDROID:
+ self.c_file_pfx = self.c_file_pfx + """
+#include <android/log.h>
+#define DEBUG_PRINT(...) __android_log_print(ANDROID_LOG_ERROR, "LDK", __VA_ARGS__)
+
+#include <unistd.h>
+#include <pthread.h>
+static void* redirect_stderr(void* ignored) {
+ int pipes[2];
+ pipe(pipes);
+ dup2(pipes[1], STDERR_FILENO);
+ char buf[8192];
+ memset(buf, 0, 8192);
+ ssize_t len;
+ while ((len = read(pipes[0], buf, 8191)) > 0) {
+ DEBUG_PRINT("%s\\n", buf);
+ memset(buf, 0, 8192);
+ }
+ DEBUG_PRINT("End of stderr???\\n");
+ return 0;
+}
+void __attribute__((constructor)) spawn_stderr_redirection() {
+ pthread_t thread;
+ pthread_create(&thread, NULL, &redirect_stderr, NULL);
+}
+"""
+ else:
+ self.c_file_pfx = self.c_file_pfx + "#define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)\n"
+
if not DEBUG:
self.c_file_pfx = self.c_file_pfx + """#define MALLOC(a, _) malloc(a)
#define FREE(p) if ((long)(p) > 1024) { free(p); }
// 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>
+"""
+
+ if self.target == Target.ANDROID:
+ self.c_file_pfx = self.c_file_pfx + """
+#include <unwind.h>
+#include <dlfcn.h>
+
+// Implement the execinfo functions using _Unwind_backtrace. This seems to miss many Rust
+// symbols, so we only use it on Android, where execinfo.h is unavailable.
+
+struct BacktraceState {
+ void** current;
+ void** end;
+};
+static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context, void* arg) {
+ struct BacktraceState* state = (struct BacktraceState*)arg;
+ uintptr_t pc = _Unwind_GetIP(context);
+ if (pc) {
+ if (state->current == state->end) {
+ return _URC_END_OF_STACK;
+ } else {
+ *state->current++ = (void*)(pc);
+ }
+ }
+ return _URC_NO_REASON;
+}
+
+int backtrace(void** buffer, int max) {
+ struct BacktraceState state = { buffer, buffer + max };
+ _Unwind_Backtrace(unwind_callback, &state);
+ return state.current - buffer;
+}
+
+void backtrace_symbols_fd(void ** buffer, int count, int _fd) {
+ for (int idx = 0; idx < count; ++idx) {
+ Dl_info info;
+ if (dladdr(buffer[idx], &info) && info.dli_sname) {
+ DEBUG_PRINT("%p: %s", buffer[idx], info.dli_sname);
+ } else {
+ DEBUG_PRINT("%p: ???", buffer[idx]);
+ }
+ }
+}
+"""
+ else:
+ self.c_file_pfx = self.c_file_pfx + "#include <execinfo.h>\n"
+ self.c_file_pfx = self.c_file_pfx + """
#include <unistd.h>
static mtx_t allocation_mtx;
const char* struct_name;
void* bt[BT_MAX];
int bt_len;
- size_t alloc_len;
+ unsigned long alloc_len;
} allocation;
static allocation* allocation_ll = NULL;
while (it->ptr != ptr) {
p = it; it = it->next;
if (it == NULL) {
- fprintf(stderr, "Tried to free unknown pointer %p at:\\n", ptr);
+ 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);
- fprintf(stderr, "\\n\\n");
+ 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
}
}
void __attribute__((destructor)) check_leaks() {
- size_t alloc_count = 0;
- size_t alloc_size = 0;
- fprintf(stderr, "The following LDK-allocated blocks still remain.\\n");
- fprintf(stderr, "Note that this is only accurate if System.gc(); System.runFinalization()\\n");
- fprintf(stderr, "was called prior to exit after all LDK objects were out of scope.\\n");
+ 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) {
- fprintf(stderr, "%s %p (%lu bytes) remains:\\n", a->struct_name, a->ptr, a->alloc_len);
+ 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);
- fprintf(stderr, "\\n\\n");
+ DEBUG_PRINT("\\n\\n");
alloc_count++;
alloc_size += a->alloc_len;
}
- fprintf(stderr, "%lu allocations remained for %lu bytes.\\n", alloc_count, alloc_size);
- fprintf(stderr, "Note that this is only accurate if System.gc(); System.runFinalization()\\n");
- fprintf(stderr, "was called prior to exit after all LDK objects were out of scope.\\n");
+ 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");
}
"""
self.c_file_pfx = self.c_file_pfx + """
static inline jstring str_ref_to_java(JNIEnv *env, const char* chars, size_t len) {
// Sadly we need to create a temporary because Java can't accept a char* without a 0-terminator
- char* err_buf = MALLOC(len + 1, "str conv buf");
- memcpy(err_buf, chars, len);
- err_buf[len] = 0;
- jstring err_conv = (*env)->NewStringUTF(env, chars);
- FREE(err_buf);
- return err_conv;
+ char* conv_buf = MALLOC(len + 1, "str conv buf");
+ memcpy(conv_buf, chars, len);
+ conv_buf[len] = 0;
+ jstring ret = (*env)->NewStringUTF(env, conv_buf);
+ FREE(conv_buf);
+ return ret;
}
static inline LDKStr java_to_owned_str(JNIEnv *env, jstring str) {
uint64_t str_len = (*env)->GetStringUTFLength(env, str);
};
return res;
}
+
+JNIEXPORT jstring JNICALL Java_org_ldk_impl_bindings_get_1lib_1version_1string(JNIEnv *env, jclass _c) {
+ return str_ref_to_java(env, "<git_version_ldk_garbagecollected>", strlen("<git_version_ldk_garbagecollected>"));
+}
+JNIEXPORT jstring JNICALL Java_org_ldk_impl_bindings_get_1ldk_1c_1bindings_1version(JNIEnv *env, jclass _c) {
+ return str_ref_to_java(env, check_get_ldk_bindings_version(), strlen(check_get_ldk_bindings_version()));
+}
+JNIEXPORT jstring JNICALL Java_org_ldk_impl_bindings_get_1ldk_1version(JNIEnv *env, jclass _c) {
+ return str_ref_to_java(env, check_get_ldk_version(), strlen(check_get_ldk_version()));
+}
"""
self.hu_struct_file_prefix = """package org.ldk.structs;