Update CI references to 0.0.122
[ldk-java] / java_strings.py
index 1f4ddaff009dfcf940632e9046082d18f5a7c8d6..b0c100e7fa6fc82e89eb1823593e7dbdab4305a2 100644 (file)
@@ -5,25 +5,36 @@ import sys
 class Target(Enum):
     JAVA = 1,
     ANDROID = 2
+    MACOS = 3
 
 class Consts:
     def __init__(self, DEBUG: bool, target: Target, **kwargs):
         self.target = target
         self.c_array_class_caches = set()
         self.c_type_map = dict(
+            bool = ['boolean'],
             uint8_t = ['byte'],
             uint16_t = ['short'],
             uint32_t = ['int'],
             uint64_t = ['long'],
+            int64_t = ['long'],
+            double = ['double'],
+        )
+        self.java_type_map = dict(
+            String = "String"
+        )
+        self.java_hu_type_map = dict(
+            String = "String"
         )
 
         self.to_hu_conv_templates = dict(
-            ptr = '{human_type} {var_name}_hu_conv = new {human_type}(null, {var_name});',
-            default = '{human_type} {var_name}_hu_conv = new {human_type}(null, {var_name});'
+            ptr = '{human_type} {var_name}_hu_conv = null; if ({var_name} < 0 || {var_name} > 4096) { {var_name}_hu_conv = new {human_type}(null, {var_name}); }',
+            default = '{human_type} {var_name}_hu_conv = null; if ({var_name} < 0 || {var_name} > 4096) { {var_name}_hu_conv = new {human_type}(null, {var_name}); }'
         )
 
         self.bindings_header = """package org.ldk.impl;
 import org.ldk.enums.*;
+import org.ldk.impl.version;
 import java.io.File;
 import java.io.InputStream;
 import java.io.IOException;
@@ -32,14 +43,6 @@ import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
 
 public class bindings {
-       public static class VecOrSliceDef {
-               public long dataptr;
-               public long datalen;
-               public long stride;
-               public VecOrSliceDef(long dataptr, long datalen, long stride) {
-                       this.dataptr = dataptr; this.datalen = datalen; this.stride = stride;
-               }
-       }
        static {
                try {
                        // Try to load natively first, this works on Android and in testing.
@@ -55,52 +58,44 @@ public class bindings {
                                Path libpath = new File(tmpdir.toPath().toString(), "liblightningjni.so").toPath();
                                Files.copy(is, libpath, StandardCopyOption.REPLACE_EXISTING);
                                Runtime.getRuntime().load(libpath.toString());
-                       } catch (IOException e) {
+                       } catch (Exception e) {
                                System.err.println("Failed to load LDK native library.");
                                System.err.println("System LDK native library load failed with: " + system_load_err);
                                System.err.println("Resource-based LDK native library load failed with: " + e);
                                throw new IllegalArgumentException(e);
                        }
                }
-               init(java.lang.Enum.class, VecOrSliceDef.class);
+               init(java.lang.Enum.class);
                init_class_cache();
-               if (!get_lib_version_string().equals(get_ldk_java_bindings_version()))
+               if (!get_lib_version_string().equals(version.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();
+               System.err.println("Loaded LDK-Java Bindings " + version.get_ldk_java_bindings_version() + " with LDK " + get_ldk_version() + " and LDK-C-Bindings " + get_ldk_c_bindings_version());
        }
-       static native void init(java.lang.Class c, java.lang.Class slicedef);
+       public static void run_statics() { /* Useful to force the statics to run */ }
+       static native void init(java.lang.Class c);
        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);
-       public static native void free_heap_ptr(long ptr);
-       public static native byte[] read_bytes(long ptr, long len);
-       public static native byte[] get_u8_slice_bytes(long slice_ptr);
-       public static native long bytes_to_u8_vec(byte[] bytes);
-       public static native long new_txpointer_copy_data(byte[] txdata);
-       public static native void txpointer_free(long ptr);
-       public static native byte[] txpointer_get_buffer(long ptr);
-       public static native long vec_slice_len(long vec);
-       public static native long new_empty_slice_vec();
-
 """
+        self.bindings_version_file = """package org.ldk.impl;
 
-        self.bindings_footer = "}\n"
+public class version {
+       public static String get_ldk_java_bindings_version() {
+               return "<git_version_ldk_garbagecollected>";
+       }
+}"""
 
         self.util_fn_pfx = """package org.ldk.structs;
 import org.ldk.impl.bindings;
+import org.ldk.enums.*;
+import org.ldk.util.*;
 import java.util.Arrays;
+import java.lang.ref.Reference;
 import javax.annotation.Nullable;
-import org.ldk.enums.*;
 
 public class UtilMethods {
 """
@@ -110,10 +105,115 @@ import java.util.LinkedList;
 class CommonBase {
        long ptr;
        LinkedList<Object> ptrs_to = new LinkedList();
-       protected CommonBase(long ptr) { assert ptr > 1024; this.ptr = ptr; }
+       protected CommonBase(long ptr) { assert ptr < 0 || ptr > 4096; this.ptr = ptr; }
 }
 """
 
+        self.txout_defn = """public class TxOut extends CommonBase {
+       /** The script_pubkey in this output */
+       public final byte[] script_pubkey;
+       /** The value, in satoshis, of this output */
+       public final long value;
+
+       TxOut(java.lang.Object _dummy, long ptr) {
+               super(ptr);
+               this.script_pubkey = bindings.TxOut_get_script_pubkey(ptr);
+               this.value = bindings.TxOut_get_value(ptr);
+       }
+       public TxOut(long value, byte[] script_pubkey) {
+               super(bindings.TxOut_new(script_pubkey, value));
+               this.script_pubkey = bindings.TxOut_get_script_pubkey(ptr);
+               this.value = bindings.TxOut_get_value(ptr);
+       }
+
+       @Override @SuppressWarnings(\"deprecation\")
+       protected void finalize() throws Throwable {
+               super.finalize();
+               if (ptr != 0) { bindings.TxOut_free(ptr); }
+       }
+}"""
+
+        self.txin_defn = """public class TxIn extends CommonBase {
+       /** The witness in this input, in serialized form */
+       public final byte[] witness;
+       /** The script_sig in this input */
+       public final byte[] script_sig;
+       /** The transaction output's sequence number */
+       public final int sequence;
+       /** The txid this input is spending */
+       public final byte[] previous_txid;
+       /** The output index within the spent transaction of the output this input is spending */
+       public final int previous_vout;
+
+       TxIn(java.lang.Object _dummy, long ptr) {
+               super(ptr);
+               this.witness = bindings.TxIn_get_witness(ptr);
+               this.script_sig = bindings.TxIn_get_script_sig(ptr);
+               this.sequence = bindings.TxIn_get_sequence(ptr);
+               this.previous_txid = bindings.TxIn_get_previous_txid(ptr);
+               this.previous_vout = bindings.TxIn_get_previous_vout(ptr);
+       }
+       /** Constructs a new TxIn, note that previous_txid must be exactly 32 bytes */
+       public TxIn(byte[] witness, byte[] script_sig, int sequence, byte[] previous_txid, int previous_vout) {
+               this(null, bindings.TxIn_new(witness, script_sig, sequence, previous_txid, previous_vout));
+       }
+
+       @Override @SuppressWarnings(\"deprecation\")
+       protected void finalize() throws Throwable {
+               super.finalize();
+               if (ptr != 0) { bindings.TxIn_free(ptr); }
+       }
+}"""
+
+        self.scalar_defn = """public class BigEndianScalar extends CommonBase {
+       /** The bytes of the scalar value, in big endian */
+       public final byte[] scalar_bytes;
+
+       BigEndianScalar(java.lang.Object _dummy, long ptr) {
+               super(ptr);
+               this.scalar_bytes = bindings.BigEndianScalar_get_bytes(ptr);
+       }
+       public BigEndianScalar(byte[] scalar_bytes) {
+               super(bindings.BigEndianScalar_new(scalar_bytes));
+               this.scalar_bytes = bindings.BigEndianScalar_get_bytes(ptr);
+       }
+
+       @Override @SuppressWarnings(\"deprecation\")
+       protected void finalize() throws Throwable {
+               super.finalize();
+               if (ptr != 0) { bindings.BigEndianScalar_free(ptr); }
+       }
+}"""
+
+        self.witness_program_defn = """public class WitnessProgram extends CommonBase {
+       /** The witness program bytes themselves */
+       public final byte[] program;
+       /** The witness version */
+       public final WitnessVersion version;
+
+       WitnessProgram(java.lang.Object _dummy, long ptr) {
+               super(ptr);
+               this.program = bindings.WitnessProgram_get_program(ptr);
+               this.version = new WitnessVersion(bindings.WitnessProgram_get_version(ptr));
+       }
+       static byte check_args(byte[] program, WitnessVersion version) {
+               if (program.length < 2 || program.length > 40) throw new IllegalArgumentException();
+               if (version.getVal() == 0 && program.length != 20 && program.length != 32) throw new IllegalArgumentException();
+               return version.getVal();
+       }
+       public WitnessProgram(byte[] program, WitnessVersion version) {
+               super(bindings.WitnessProgram_new(check_args(program, version), program));
+               this.program = bindings.WitnessProgram_get_program(ptr);
+               this.version = new WitnessVersion(bindings.WitnessProgram_get_version(ptr));
+       }
+
+       @Override @SuppressWarnings(\"deprecation\")
+       protected void finalize() throws Throwable {
+               super.finalize();
+               if (ptr != 0) { bindings.WitnessProgram_free(ptr); }
+       }
+}"""
+
         self.c_file_pfx = """#include <jni.h>
 // On OSX jlong (ie long long) is not equivalent to int64_t, so we override here
 #define int64_t jlong
@@ -123,6 +223,9 @@ class CommonBase {
 #include <stdatomic.h>
 #include <stdlib.h>
 
+#define LIKELY(v) __builtin_expect(!!(v), 1)
+#define UNLIKELY(v) __builtin_expect(!!(v), 0)
+
 """
 
         if self.target == Target.ANDROID:
@@ -154,9 +257,11 @@ void __attribute__((constructor)) spawn_stderr_redirection() {
         else:
             self.c_file_pfx = self.c_file_pfx + "#define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)\n"
 
-        if not DEBUG or sys.platform == "darwin":
+        if not DEBUG or self.target == Target.MACOS:
             self.c_file_pfx = self.c_file_pfx + """#define MALLOC(a, _) malloc(a)
-#define FREE(p) if ((uint64_t)(p) > 1024) { free(p); }
+#define FREE(p) if ((uint64_t)(p) > 4096) { free(p); }
+#define CHECK_ACCESS(p)
+#define CHECK_INNER_FIELD_ACCESS_OR_NULL(v)
 """
         if not DEBUG:
             self.c_file_pfx += """#define DO_ASSERT(a) (void)(a)
@@ -174,11 +279,10 @@ void __attribute__((constructor)) debug_log_version() {
                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());
 }
 """
 
-            if sys.platform != "darwin":
+            if self.target != Target.MACOS:
                 self.c_file_pfx += """
 // 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
@@ -233,10 +337,11 @@ void backtrace_symbols_fd(void ** buffer, int count, int _fd) {
                     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;
+#include <pthread.h>
+static pthread_mutex_t allocation_mtx;
 
 void __attribute__((constructor)) init_mtx() {
-       DO_ASSERT(mtx_init(&allocation_mtx, mtx_plain) == thrd_success);
+       DO_ASSERT(!pthread_mutex_init(&allocation_mtx, NULL));
 }
 
 #define BT_MAX 128
@@ -258,10 +363,10 @@ static void new_allocation(void* res, const char* struct_name, size_t len) {
        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);
+       DO_ASSERT(!pthread_mutex_lock(&allocation_mtx));
        new_alloc->next = allocation_ll;
        allocation_ll = new_alloc;
-       DO_ASSERT(mtx_unlock(&allocation_mtx) == thrd_success);
+       DO_ASSERT(!pthread_mutex_unlock(&allocation_mtx));
 }
 static void* MALLOC(size_t len, const char* struct_name) {
        void* res = __real_malloc(len);
@@ -271,27 +376,27 @@ static void* MALLOC(size_t len, const char* struct_name) {
 void __real_free(void* ptr);
 static void alloc_freed(void* ptr) {
        allocation* p = NULL;
-       DO_ASSERT(mtx_lock(&allocation_mtx) == thrd_success);
+       DO_ASSERT(!pthread_mutex_lock(&allocation_mtx));
        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);
+                       DEBUG_PRINT("ERROR: 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);
+                       DO_ASSERT(!pthread_mutex_unlock(&allocation_mtx));
                        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(!pthread_mutex_unlock(&allocation_mtx));
        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
+       if ((uint64_t)ptr <= 4096) return; // Rust loves to create pointers to the NULL page for dummys
        alloc_freed(ptr);
        __real_free(ptr);
 }
@@ -312,6 +417,31 @@ void __wrap_free(void* ptr) {
        __real_free(ptr);
 }
 
+static void CHECK_ACCESS(const void* ptr) {
+       DO_ASSERT(!pthread_mutex_lock(&allocation_mtx));
+       allocation* it = allocation_ll;
+       while (it->ptr != ptr) {
+               it = it->next;
+               if (it == NULL) {
+                       DEBUG_PRINT("ERROR: Tried to access 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(!pthread_mutex_unlock(&allocation_mtx));
+                       return; // addrsan should catch and print more info than we have
+               }
+       }
+       DO_ASSERT(!pthread_mutex_unlock(&allocation_mtx));
+}
+#define CHECK_INNER_FIELD_ACCESS_OR_NULL(v) \\
+       if (v.is_owned && v.inner != NULL) { \\
+               const void *p = __unmangle_inner_ptr(v.inner); \\
+               if (p != NULL) { \\
+                       CHECK_ACCESS(p); \\
+               } \\
+       }
+
 void* __real_realloc(void* ptr, size_t newlen);
 void* __wrap_realloc(void* ptr, size_t len) {
        if (ptr != NULL) alloc_freed(ptr);
@@ -344,84 +474,9 @@ void __attribute__((destructor)) check_leaks() {
 """
         self.c_file_pfx = self.c_file_pfx + """
 static jmethodID ordinal_meth = NULL;
-static jmethodID slicedef_meth = NULL;
-static jclass slicedef_cls = NULL;
-JNIEXPORT void Java_org_ldk_impl_bindings_init(JNIEnv * env, jclass _b, jclass enum_class, jclass slicedef_class) {
+JNIEXPORT void Java_org_ldk_impl_bindings_init(JNIEnv * env, jclass _b, jclass enum_class) {
        ordinal_meth = (*env)->GetMethodID(env, enum_class, "ordinal", "()I");
        CHECK(ordinal_meth != NULL);
-       slicedef_meth = (*env)->GetMethodID(env, slicedef_class, "<init>", "(JJJ)V");
-       CHECK(slicedef_meth != NULL);
-       slicedef_cls = (*env)->NewGlobalRef(env, slicedef_class);
-       CHECK(slicedef_cls != NULL);
-}
-
-JNIEXPORT jboolean JNICALL Java_org_ldk_impl_bindings_deref_1bool (JNIEnv * env, jclass _a, jlong ptr) {
-       return *((bool*)ptr);
-}
-JNIEXPORT jlong JNICALL Java_org_ldk_impl_bindings_deref_1long (JNIEnv * env, jclass _a, jlong ptr) {
-       return *((long*)ptr);
-}
-JNIEXPORT void JNICALL Java_org_ldk_impl_bindings_free_1heap_1ptr (JNIEnv * env, jclass _a, jlong ptr) {
-       FREE((void*)ptr);
-}
-JNIEXPORT jbyteArray JNICALL Java_org_ldk_impl_bindings_read_1bytes (JNIEnv * env, jclass _b, jlong ptr, jlong len) {
-       jbyteArray ret_arr = (*env)->NewByteArray(env, len);
-       (*env)->SetByteArrayRegion(env, ret_arr, 0, len, (unsigned char*)ptr);
-       return ret_arr;
-}
-JNIEXPORT jbyteArray JNICALL Java_org_ldk_impl_bindings_get_1u8_1slice_1bytes (JNIEnv * env, jclass _b, jlong slice_ptr) {
-       LDKu8slice *slice = (LDKu8slice*)slice_ptr;
-       jbyteArray ret_arr = (*env)->NewByteArray(env, slice->datalen);
-       (*env)->SetByteArrayRegion(env, ret_arr, 0, slice->datalen, slice->data);
-       return ret_arr;
-}
-JNIEXPORT int64_t impl_bindings_bytes_1to_1u8_1vec (JNIEnv * env, jclass _b, jbyteArray bytes) {
-       LDKCVec_u8Z *vec = (LDKCVec_u8Z*)MALLOC(sizeof(LDKCVec_u8Z), "LDKCVec_u8");
-       vec->datalen = (*env)->GetArrayLength(env, bytes);
-       vec->data = (uint8_t*)MALLOC(vec->datalen, "LDKCVec_u8Z Bytes");
-       (*env)->GetByteArrayRegion (env, bytes, 0, vec->datalen, vec->data);
-       return (uint64_t)vec;
-}
-JNIEXPORT jbyteArray JNICALL Java_org_ldk_impl_bindings_txpointer_1get_1buffer (JNIEnv * env, jclass _b, jlong ptr) {
-       LDKTransaction *txdata = (LDKTransaction*)ptr;
-       LDKu8slice slice;
-       slice.data = txdata->data;
-       slice.datalen = txdata->datalen;
-       return Java_org_ldk_impl_bindings_get_1u8_1slice_1bytes(env, _b, (uint64_t)&slice);
-}
-JNIEXPORT int64_t JNICALL Java_org_ldk_impl_bindings_new_1txpointer_1copy_1data (JNIEnv * env, jclass _b, jbyteArray bytes) {
-       LDKTransaction *txdata = (LDKTransaction*)MALLOC(sizeof(LDKTransaction), "LDKTransaction");
-       txdata->datalen = (*env)->GetArrayLength(env, bytes);
-       txdata->data = (uint8_t*)MALLOC(txdata->datalen, "Tx Data Bytes");
-       txdata->data_is_owned = false;
-       (*env)->GetByteArrayRegion (env, bytes, 0, txdata->datalen, txdata->data);
-       return (uint64_t)txdata;
-}
-JNIEXPORT void JNICALL Java_org_ldk_impl_bindings_txpointer_1free (JNIEnv * env, jclass _b, jlong ptr) {
-       LDKTransaction *tx = (LDKTransaction*)ptr;
-       tx->data_is_owned = true;
-       Transaction_free(*tx);
-       FREE((void*)ptr);
-}
-JNIEXPORT jlong JNICALL Java_org_ldk_impl_bindings_vec_1slice_1len (JNIEnv * env, jclass _a, jlong ptr) {
-       // Check offsets of a few Vec types are all consistent as we're meant to be generic across types
-       _Static_assert(offsetof(LDKCVec_u8Z, datalen) == offsetof(LDKCVec_SignatureZ, datalen), "Vec<*> needs to be mapped identically");
-       _Static_assert(offsetof(LDKCVec_u8Z, datalen) == offsetof(LDKCVec_MessageSendEventZ, datalen), "Vec<*> needs to be mapped identically");
-       _Static_assert(offsetof(LDKCVec_u8Z, datalen) == offsetof(LDKCVec_EventZ, datalen), "Vec<*> needs to be mapped identically");
-       _Static_assert(offsetof(LDKCVec_u8Z, datalen) == offsetof(LDKCVec_C2Tuple_usizeTransactionZZ, datalen), "Vec<*> needs to be mapped identically");
-       LDKCVec_u8Z *vec = (LDKCVec_u8Z*)ptr;
-       return (uint64_t)vec->datalen;
-}
-JNIEXPORT int64_t JNICALL Java_org_ldk_impl_bindings_new_1empty_1slice_1vec (JNIEnv * env, jclass _b) {
-       // Check sizes of a few Vec types are all consistent as we're meant to be generic across types
-       _Static_assert(sizeof(LDKCVec_u8Z) == sizeof(LDKCVec_SignatureZ), "Vec<*> needs to be mapped identically");
-       _Static_assert(sizeof(LDKCVec_u8Z) == sizeof(LDKCVec_MessageSendEventZ), "Vec<*> needs to be mapped identically");
-       _Static_assert(sizeof(LDKCVec_u8Z) == sizeof(LDKCVec_EventZ), "Vec<*> needs to be mapped identically");
-       _Static_assert(sizeof(LDKCVec_u8Z) == sizeof(LDKCVec_C2Tuple_usizeTransactionZZ), "Vec<*> needs to be mapped identically");
-       LDKCVec_u8Z *vec = (LDKCVec_u8Z*)MALLOC(sizeof(LDKCVec_u8Z), "Empty LDKCVec");
-       vec->data = NULL;
-       vec->datalen = 0;
-       return (uint64_t)vec;
 }
 
 // We assume that CVec_u8Z and u8slice are the same size and layout (and thus pointers to the two can be mixed)
@@ -435,41 +490,151 @@ _Static_assert(sizeof(void*) <= 8, "Pointers must fit into 64 bits");
 
 typedef jlongArray int64_tArray;
 typedef jbyteArray int8_tArray;
-
-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* 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);
+typedef jshortArray int16_tArray;
+
+static inline jstring str_ref_to_java(JNIEnv *env, const unsigned char* chars, size_t len) {
+       // Java uses "Modified UTF-8" rather than UTF-8. This requires special
+       // handling for codepoints above 0xFFFF, which get converted from four
+       // bytes to six. We don't know upfront how many codepoints in the string
+       // are above 0xFFFF, so we just allocate an extra 33% up front and waste a
+       // bit of space.
+       unsigned char* java_chars = MALLOC(len * 3 / 2 + 1, "str conv buf");
+       unsigned char* next_java_char = java_chars;
+       const unsigned char* next_in_char = chars;
+       const unsigned char* end = chars + len;
+       #define COPY_CHAR_TO_JAVA do { *next_java_char = *next_in_char; next_java_char++; next_in_char++; } while (0)
+
+       while (next_in_char < end) {
+               if (!*next_in_char) break;
+               if (!(*next_in_char & 0b10000000)) {
+                       COPY_CHAR_TO_JAVA;
+               } else if ((*next_in_char & 0b11100000) == 0b11000000) {
+                       if (next_in_char + 2 > end) { CHECK(false); break; } // bad string
+                       COPY_CHAR_TO_JAVA;
+                       COPY_CHAR_TO_JAVA;
+               } else if ((*next_in_char & 0b11110000) == 0b11100000) {
+                       if (next_in_char + 3 > end) { CHECK(false); break; } // bad string
+                       COPY_CHAR_TO_JAVA;
+                       COPY_CHAR_TO_JAVA;
+                       COPY_CHAR_TO_JAVA;
+               } else if ((*next_in_char & 0b11111000) == 0b11110000) {
+                       if (next_in_char + 4 > end) { CHECK(false); break; } // bad string
+                       uint32_t codepoint = 0;
+                       codepoint |= (((uint32_t)*(next_in_char    )) & 0b00000111) << 18;
+                       codepoint |= (((uint32_t)*(next_in_char + 1)) & 0b00111111) << 12;
+                       codepoint |= (((uint32_t)*(next_in_char + 2)) & 0b00111111) << 6;
+                       codepoint |= (((uint32_t)*(next_in_char + 3)) & 0b00111111) << 0;
+                       codepoint -= 0x10000;
+                       *next_java_char = 0b11101101;
+                       next_java_char++;
+                       *next_java_char = 0b10100000 | ((codepoint >> 16) & 0b00001111);
+                       next_java_char++;
+                       *next_java_char = 0b10000000 | ((codepoint >> 10) & 0b00111111);
+                       next_java_char++;
+                       *next_java_char = 0b11101101;
+                       next_java_char++;
+                       *next_java_char = 0b10110000 | ((codepoint >>  6) & 0b00001111);
+                       next_java_char++;
+                       *next_java_char = 0b10000000 | ((codepoint >>  0) & 0b00111111);
+                       next_java_char++;
+                       next_in_char += 4;
+               } else {
+                       // Bad string
+                       CHECK(false);
+                       break;
+               }
+       }
+       *next_java_char = 0;
+       jstring ret = (*env)->NewStringUTF(env, java_chars);
+       FREE(java_chars);
        return ret;
 }
 static inline LDKStr java_to_owned_str(JNIEnv *env, jstring str) {
        uint64_t str_len = (*env)->GetStringUTFLength(env, str);
-       char* newchars = MALLOC(str_len + 1, "String chars");
-       const char* jchars = (*env)->GetStringUTFChars(env, str, NULL);
-       memcpy(newchars, jchars, str_len);
-       newchars[str_len] = 0;
+       // Java uses "Modified UTF-8" rather than UTF-8. This requires special
+       // handling for codepoints above 0xFFFF, which we implement below.
+       unsigned char* newchars = MALLOC(str_len, "String chars");
+       unsigned char* next_newchar = newchars;
+       uint64_t utf8_len = 0;
+
+       const unsigned char* jchars = (*env)->GetStringUTFChars(env, str, NULL);
+       const unsigned char* next_char = jchars;
+       const unsigned char* end = jchars + str_len;
+
+       #define COPY_CHAR_FROM_JAVA do { *next_newchar = *next_char; next_newchar++; next_char++; utf8_len++; } while (0)
+
+       while (next_char < end) {
+               if (!(*next_char & 0b10000000)) {
+                       CHECK(*next_char != 0); // Bad Modified UTF-8 string, but we'll just cut here
+                       COPY_CHAR_FROM_JAVA;
+               } else if ((*next_char & 0b11100000) == 0b11000000) {
+                       if (next_char + 2 > end) { CHECK(false); break; } // bad string
+                       uint16_t codepoint = 0;
+                       codepoint |= (((uint16_t)(*next_char & 0x1f)) << 6);
+                       codepoint |= *(next_char + 1) & 0x3f;
+                       if (codepoint == 0) {
+                               // We should really never get null codepoints, but java allows them.
+                               // Just skip it.
+                               next_char += 2;
+                       } else {
+                               COPY_CHAR_FROM_JAVA;
+                               COPY_CHAR_FROM_JAVA;
+                       }
+               } else if ((*next_char & 0b11110000) == 0b11100000) {
+                       if (next_char + 3 > end) { CHECK(false); break; } // bad string
+                       if (*next_char == 0b11101101 && (*(next_char + 1) & 0b11110000) == 0b10100000) {
+                               // Surrogate code unit shoul indicate we have a codepoint above
+                               // 0xFFFF, which is where Modified UTF-8 and UTF-8 diverge.
+                               if (next_char + 6 > end) { CHECK(false); break; } // bad string
+                               CHECK(*(next_char + 3) == 0b11101101);
+                               CHECK((*(next_char + 4) & 0b11110000) == 0b10110000);
+                               // Calculate the codepoint per https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16542
+                               uint32_t codepoint = 0x10000;
+                               codepoint += ((((uint32_t)*(next_char + 1)) & 0x0f) << 16);
+                               codepoint += ((((uint32_t)*(next_char + 2)) & 0x3f) << 10);
+                               codepoint += ((((uint32_t)*(next_char + 4)) & 0x0f) <<  6);
+                               codepoint +=  (((uint32_t)*(next_char + 5)) & 0x3f);
+                               *next_newchar = 0b11110000 | ((codepoint >> 18) &    0b111);
+                               next_newchar++;
+                               *next_newchar = 0b10000000 | ((codepoint >> 12) & 0b111111);
+                               next_newchar++;
+                               *next_newchar = 0b10000000 | ((codepoint >>  6) & 0b111111);
+                               next_newchar++;
+                               *next_newchar = 0b10000000 | ( codepoint        & 0b111111);
+                               next_newchar++;
+                               next_char += 6;
+                               utf8_len += 4;
+                       } else {
+                               COPY_CHAR_FROM_JAVA;
+                               COPY_CHAR_FROM_JAVA;
+                               COPY_CHAR_FROM_JAVA;
+                       }
+               } else {
+                       // Bad string
+                       CHECK(false);
+                       break;
+               }
+       }
        (*env)->ReleaseStringUTFChars(env, str, jchars);
        LDKStr res = {
                .chars = newchars,
-               .len = str_len,
+               .len = utf8_len,
                .chars_is_owned = true
        };
        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()));
 }
+#include "version.c"
 """
+        self.c_version_file = """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>"));
+}"""
 
         self.hu_struct_file_prefix = """package org.ldk.structs;
 
@@ -477,19 +642,28 @@ import org.ldk.impl.bindings;
 import org.ldk.enums.*;
 import org.ldk.util.*;
 import java.util.Arrays;
+import java.lang.ref.Reference;
 import javax.annotation.Nullable;
 
 """
+        self.hu_struct_file_suffix = ""
         self.c_fn_ty_pfx = "JNIEXPORT "
         self.c_fn_args_pfx = "JNIEnv *env, jclass clz"
         self.file_ext = ".java"
         self.ptr_c_ty = "int64_t"
         self.ptr_native_ty = "long"
-        self.result_c_ty = "jclass"
+        self.u128_native_ty = "UInt128"
+        self.usize_c_ty = "int64_t"
+        self.usize_native_ty = "long"
+        self.native_zero_ptr = "0"
+        self.unitary_enum_c_ty = "jclass"
         self.ptr_arr = "jobjectArray"
         self.is_arr_some_check = ("", " != NULL")
         self.get_native_arr_len_call = ("(*env)->GetArrayLength(env, ", ")")
 
+    def bindings_footer(self):
+        return "}\n"
+
     def construct_jenv(self):
         res =  "JNIEnv *env;\n"
         res += "jint get_jenv_res = (*j_calls->vm)->GetEnv(j_calls->vm, (void**)&env, JNI_VERSION_1_6);\n"
@@ -519,19 +693,26 @@ import javax.annotation.Nullable;
             clz_var = ty_info.java_fn_ty_arg[1:].replace("[", "arr_of_")
             self.c_array_class_caches.add(clz_var)
             return "(*env)->NewObjectArray(env, " + arr_len + ", " + clz_var + "_clz, NULL);\n"
+        elif ty_info.subty.c_ty == "jstring":
+            return "(*env)->NewObjectArray(env, " + arr_len + ", String_clz, NULL);\n"
         else:
             return "(*env)->New" + ty_info.java_ty.strip("[]").title() + "Array(env, " + arr_len + ")"
     def set_native_arr_contents(self, arr_name, arr_len, ty_info):
         if ty_info.c_ty == "int8_tArray":
             return ("(*env)->SetByteArrayRegion(env, " + arr_name + ", 0, " + arr_len + ", ", ")")
+        elif ty_info.c_ty == "int16_tArray":
+            return ("(*env)->SetShortArrayRegion(env, " + arr_name + ", 0, " + arr_len + ", ", ")")
         else:
             assert False
     def get_native_arr_contents(self, arr_name, dest_name, arr_len, ty_info, copy):
-        if ty_info.c_ty == "int8_tArray":
+        if "String" in ty_info.java_ty:
+            return None
+        if ty_info.c_ty == "int8_tArray" or ty_info.c_ty == "int16_tArray":
+            fn_ty = "Byte" if ty_info.c_ty == "int8_tArray" else "Short"
             if copy:
-                return "(*env)->GetByteArrayRegion(env, " + arr_name + ", 0, " + arr_len + ", " + dest_name + ")"
+                return "(*env)->Get" + fn_ty + "ArrayRegion(env, " + arr_name + ", 0, " + arr_len + ", " + dest_name + ")"
             else:
-                return "(*env)->GetByteArrayElements (env, " + arr_name + ", NULL)"
+                return "(*env)->Get" + fn_ty + "ArrayElements (env, " + arr_name + ", NULL)"
         elif not ty_info.java_ty[:len(ty_info.java_ty) - 2].endswith("[]"):
             return "(*env)->Get" + ty_info.subty.java_ty.title() + "ArrayElements (env, " + arr_name + ", NULL)"
         else:
@@ -555,10 +736,26 @@ import javax.annotation.Nullable;
         else:
             return "(*env)->Release" + ty_info.java_ty.strip("[]").title() + "ArrayElements(env, " + arr_name + ", " + dest_name + ", 0)"
 
+    def map_hu_array_elems(self, arr_name, conv_name, arr_ty, elem_ty, is_nullable):
+        if elem_ty.java_ty == "long" and elem_ty.java_hu_ty != "long":
+            return arr_name + " != null ? Arrays.stream(" + arr_name + ").mapToLong(" + conv_name + " -> " + elem_ty.from_hu_conv[0] + ").toArray() : null"
+        elif elem_ty.java_ty == "long":
+            return arr_name + " != null ? Arrays.stream(" + arr_name + ").map(" + conv_name + " -> " + elem_ty.from_hu_conv[0] + ").toArray() : null"
+        elif elem_ty.java_hu_ty == "UInt5":
+            return arr_name + " != null ? InternalUtils.convUInt5Array(" + arr_name + ") : null"
+        elif elem_ty.java_hu_ty == "WitnessVersion":
+            return arr_name + " != null ? InternalUtils.convWitnessVersionArray(" + arr_name + ") : null"
+        else:
+            return arr_name + " != null ? Arrays.stream(" + arr_name + ").map(" + conv_name + " -> " + elem_ty.from_hu_conv[0] + ").toArray(" + arr_ty.java_ty + "::new) : null"
+
     def str_ref_to_native_call(self, var_name, str_len):
         return "str_ref_to_java(env, " + var_name + ", " + str_len + ")"
     def str_ref_to_c_call(self, var_name):
         return "java_to_owned_str(env, " + var_name + ")"
+    def str_to_hu_conv(self, var_name):
+        return None
+    def str_from_hu_conv(self, var_name):
+        return None
 
     def c_fn_name_define_pfx(self, fn_name, has_args):
         if has_args:
@@ -569,51 +766,124 @@ import javax.annotation.Nullable;
         res = ""
         for ty in sorted(self.c_array_class_caches):
             res = res + "static jclass " + ty + "_clz = NULL;\n"
+        res = res + "static jclass String_clz = NULL;\n"
         res = res + "JNIEXPORT void Java_org_ldk_impl_bindings_init_1class_1cache(JNIEnv * env, jclass clz) {\n"
         for ty in sorted(self.c_array_class_caches):
             res = res + "\t" + ty + "_clz = (*env)->FindClass(env, \"" + ty.replace("arr_of_", "[") + "\");\n"
             res = res + "\tCHECK(" + ty + "_clz != NULL);\n"
             res = res + "\t" + ty + "_clz = (*env)->NewGlobalRef(env, " + ty + "_clz);\n"
+        res = res + "\tString_clz = (*env)->FindClass(env, \"java/lang/String\");\n"
+        res = res + "\tCHECK(String_clz != NULL);\n"
+        res = res + "\tString_clz = (*env)->NewGlobalRef(env, String_clz);\n"
+
         res = res + "}\n"
         return res
 
+    def var_decl_statement(self, ty_string, var_name, statement):
+        return ty_string + " " + var_name + " = " + statement
+
+    def get_java_arr_len(self, arr_name):
+        return arr_name + ".length"
+    def get_java_arr_elem(self, elem_ty, arr_name, idx):
+        return arr_name + "[" + idx + "]"
+    def constr_hu_array(self, ty_info, arr_len):
+        base_ty = ty_info.subty.java_hu_ty.split("[")[0].split("<")[0]
+        conv = "new " + base_ty + "[" + arr_len + "]"
+        if "[" in ty_info.subty.java_hu_ty.split("<")[0]:
+            # Do a bit of a dance to move any excess [] to the end
+            conv += "[" + ty_info.subty.java_hu_ty.split("<")[0].split("[")[1]
+        return conv
+    def cleanup_converted_native_array(self, ty_info, arr_name):
+        return None
+
+    def primitive_arr_from_hu(self, arr_ty, fixed_len, arr_name):
+        mapped_ty = arr_ty.subty
+        if arr_ty.rust_obj == "LDKU128":
+            return ("" + arr_name + ".getLEBytes()", "")
+        if fixed_len is not None:
+            if mapped_ty.c_ty == "int8_t" or mapped_ty.c_ty == "uint8_t":
+                return ("InternalUtils.check_arr_len(" + arr_name + ", " + fixed_len + ")", "")
+            elif mapped_ty.c_ty == "int16_t" or mapped_ty.c_ty == "uint16_t":
+                return ("InternalUtils.check_arr_16_len(" + arr_name + ", " + fixed_len + ")", "")
+            else:
+                print(arr_ty.c_ty)
+                assert False
+        return None
+    def primitive_arr_to_hu(self, arr_ty, fixed_len, arr_name, conv_name):
+        if arr_ty.rust_obj == "LDKU128":
+            return "org.ldk.util.UInt128 " + conv_name + " = new org.ldk.util.UInt128(" + arr_name + ");"
+        return None
+
+    def java_arr_ty_str(self, elem_ty_str):
+        return elem_ty_str + "[]"
+
+    def for_n_in_range(self, n, minimum, maximum):
+        return "for (int " + n + " = " + minimum + "; " + n + " < " + maximum + "; " + n + "++) {"
+    def for_n_in_arr(self, n, arr_name, arr_elem_ty):
+        return ("for (" + arr_elem_ty.java_hu_ty + " " + n + ": " + arr_name + ") { ", " }")
+
+    def get_ptr(self, var):
+        return var + ".ptr"
+    def set_null_skip_free(self, var):
+        return var + ".ptr" + " = 0;"
+
+    def add_ref(self, holder, referent):
+        return "if (" + holder + " != null) { " + holder + ".ptrs_to.add(" + referent + "); }"
+
+    def fully_qualified_hu_ty_path(self, ty):
+        if ty.java_fn_ty_arg.startswith("L") and ty.java_fn_ty_arg.endswith(";"):
+            return ty.java_fn_ty_arg.strip("L;").replace("/", ".")
+        if ty.java_hu_ty == "UnqualifiedError" or ty.java_hu_ty == "UInt128" or ty.java_hu_ty == "UInt5" or ty.java_hu_ty == "WitnessVersion":
+            return "org.ldk.util." + ty.java_hu_ty
+        if not ty.is_native_primitive and ty.rust_obj is not None and not "[]" in ty.java_hu_ty:
+            return "org.ldk.structs." + ty.java_hu_ty
+        return ty.java_hu_ty
+
     def native_c_unitary_enum_map(self, struct_name, variants, enum_doc_comment):
         out_java_enum = "package org.ldk.enums;\n\n"
         out_java = ""
         out_c = ""
-        out_c = out_c + "static inline LDK" + struct_name + " LDK" + struct_name + "_from_java(" + self.c_fn_args_pfx + ") {\n"
-        out_c = out_c + "\tswitch ((*env)->CallIntMethod(env, clz, ordinal_meth)) {\n"
+        out_c += "static inline LDK" + struct_name + " LDK" + struct_name + "_from_java(" + self.c_fn_args_pfx + ") {\n"
+        out_c += "\tjint ord = (*env)->CallIntMethod(env, clz, ordinal_meth);\n"
+        out_c += "\tif (UNLIKELY((*env)->ExceptionCheck(env))) {\n"
+        out_c += "\t\t(*env)->ExceptionDescribe(env);\n"
+        out_c += "\t\t(*env)->FatalError(env, \"A call to " + struct_name + ".ordinal() from rust threw an exception.\");\n"
+        out_c += "\t}\n"
+        out_c += "\tswitch (ord) {\n"
 
         if enum_doc_comment is not None:
             out_java_enum += "/**\n * " + enum_doc_comment.replace("\n", "\n * ") + "\n */\n"
         out_java_enum += "public enum " + struct_name + " {\n"
         ord_v = 0
-        for var in variants:
-            out_java_enum = out_java_enum + "\t" + var + ",\n"
+        for var, var_docs in variants:
+            if var_docs is not None:
+                out_java_enum += "\t/**\n\t * " + var_docs.replace("\n", "\n\t * ") + "\n\t */\n"
+            out_java_enum += "\t" + var + ",\n"
             out_c = out_c + "\t\tcase %d: return %s;\n" % (ord_v, var)
             ord_v = ord_v + 1
         out_java_enum = out_java_enum + "\t; static native void init();\n"
-        out_java_enum = out_java_enum + "\tstatic { init(); }\n"
+        out_java_enum = out_java_enum + "\tstatic { org.ldk.impl.bindings.run_statics(); init(); }\n"
         out_java_enum = out_java_enum + "}"
         out_java = out_java + "\tstatic { " + struct_name + ".values(); /* Force enum statics to run */ }\n"
-        out_c = out_c + "\t}\n"
-        out_c = out_c + "\tabort();\n"
-        out_c = out_c + "}\n"
+        out_c += "\t}\n"
+        out_c += "\t(*env)->FatalError(env, \"A call to " + struct_name + ".ordinal() from rust returned an invalid value.\");\n"
+        out_c += "\tabort(); // Unreachable, but will let the compiler know we don't return here\n"
+        out_c += "}\n"
 
         out_c = out_c + "static jclass " + struct_name + "_class = NULL;\n"
-        for var in variants:
+        for var, _ in variants:
             out_c = out_c + "static jfieldID " + struct_name + "_" + var + " = NULL;\n"
         out_c = out_c + self.c_fn_ty_pfx + "void JNICALL Java_org_ldk_enums_" + struct_name.replace("_", "_1") + "_init (" + self.c_fn_args_pfx + ") {\n"
         out_c = out_c + "\t" + struct_name + "_class = (*env)->NewGlobalRef(env, clz);\n"
         out_c = out_c + "\tCHECK(" + struct_name + "_class != NULL);\n"
-        for var in variants:
+        for var, _ in variants:
             out_c = out_c + "\t" + struct_name + "_" + var + " = (*env)->GetStaticFieldID(env, " + struct_name + "_class, \"" + var + "\", \"Lorg/ldk/enums/" + struct_name + ";\");\n"
             out_c = out_c + "\tCHECK(" + struct_name + "_" + var + " != NULL);\n"
         out_c = out_c + "}\n"
         out_c = out_c + "static inline jclass LDK" + struct_name + "_to_java(JNIEnv *env, LDK" + struct_name + " val) {\n"
         out_c = out_c + "\tswitch (val) {\n"
         ord_v = 0
-        for var in variants:
+        for var, _ in variants:
             out_c = out_c + "\t\tcase " + var + ":\n"
             out_c = out_c + "\t\t\treturn (*env)->GetStaticObjectField(env, " + struct_name + "_class, " + struct_name + "_" + var + ");\n"
             ord_v = ord_v + 1
@@ -635,14 +905,11 @@ import javax.annotation.Nullable;
             out_c = out_c + "static jmethodID " + struct_name + "_" + var + "_meth = NULL;\n"
         out_c = out_c + self.c_fn_ty_pfx + "void JNICALL Java_org_ldk_impl_bindings_00024" + struct_name.replace("_", "_1") + "_init (" + self.c_fn_args_pfx + ") {\n"
         for var_name in variants:
-            out_c = out_c + "\t" + struct_name + "_" + var_name + "_class =\n"
-            if self.target == Target.ANDROID:
-                out_c = out_c + "\t\t(*env)->NewGlobalRef(env, (*env)->FindClass(env, \"org/ldk/impl/bindings$" + struct_name + "$" + var_name + "\"));\n"
-            else:
-                out_c = out_c + "\t\t(*env)->NewGlobalRef(env, (*env)->FindClass(env, \"Lorg/ldk/impl/bindings$" + struct_name + "$" + var_name + ";\"));\n"
-            out_c = out_c + "\tCHECK(" + struct_name + "_" + var_name + "_class != NULL);\n"
-            out_c = out_c + "\t" + struct_name + "_" + var_name + "_meth = (*env)->GetMethodID(env, " + struct_name + "_" + var_name + "_class, \"<init>\", \"(" + init_meth_jty_strs[var_name] + ")V\");\n"
-            out_c = out_c + "\tCHECK(" + struct_name + "_" + var_name + "_meth != NULL);\n"
+            out_c += "\t" + struct_name + "_" + var_name + "_class =\n"
+            out_c += "\t\t(*env)->NewGlobalRef(env, (*env)->FindClass(env, \"org/ldk/impl/bindings$" + struct_name + "$" + var_name + "\"));\n"
+            out_c += "\tCHECK(" + struct_name + "_" + var_name + "_class != NULL);\n"
+            out_c += "\t" + struct_name + "_" + var_name + "_meth = (*env)->GetMethodID(env, " + struct_name + "_" + var_name + "_class, \"<init>\", \"(" + init_meth_jty_strs[var_name] + ")V\");\n"
+            out_c += "\tCHECK(" + struct_name + "_" + var_name + "_meth != NULL);\n"
         out_c = out_c + "}\n"
         return out_c
 
@@ -691,12 +958,26 @@ import javax.annotation.Nullable;
                     out_java_trait = out_java_trait + "\t\t" + var.from_hu_conv[1].replace("\n", "\n\t\t") + ";\n"
             else:
                 out_java_trait = out_java_trait + "\t\tthis.ptrs_to.add(" + var[1] + ");\n"
-        out_java_trait = out_java_trait + "\t\tthis.bindings_instance = arg;\n"
-        out_java_trait = out_java_trait + "\t}\n"
-        out_java_trait = out_java_trait + "\t@Override @SuppressWarnings(\"deprecation\")\n"
-        out_java_trait = out_java_trait + "\tprotected void finalize() throws Throwable {\n"
-        out_java_trait = out_java_trait + "\t\tif (ptr != 0) { bindings." + struct_name.replace("LDK","") + "_free(ptr); } super.finalize();\n"
-        out_java_trait = out_java_trait + "\t}\n\n"
+        out_java_trait += f"""         this.bindings_instance = arg;
+       }}
+       @Override @SuppressWarnings("deprecation")
+       protected void finalize() throws Throwable {{
+               if (ptr != 0) {{ bindings.{struct_name.replace("LDK","")}_free(ptr); }} super.finalize();
+       }}
+       /**
+        * Destroys the object, freeing associated resources. After this call, any access
+        * to this object may result in a SEGFAULT or worse.
+        *
+        * You should generally NEVER call this method. You should let the garbage collector
+        * do this for you when it finalizes objects. However, it may be useful for types
+        * which represent locks and should be closed immediately to avoid holding locks
+        * until the GC runs.
+        */
+       public void destroy() {{
+               if (ptr != 0) {{ bindings.{struct_name.replace("LDK","")}_free(ptr); }}
+               ptr = 0;
+       }}
+"""
 
         java_trait_constr = "\tprivate static class " + struct_name + "Holder { " + struct_name.replace("LDK", "") + " held; }\n"
         java_trait_constr = java_trait_constr + "\tpublic static " + struct_name.replace("LDK", "") + " new_impl(" + struct_name.replace("LDK", "") + "Interface arg"
@@ -753,7 +1034,8 @@ import javax.annotation.Nullable;
                     else:
                         java_trait_constr = java_trait_constr + arg_info.arg_name
 
-                java_trait_constr = java_trait_constr + ");\n"
+                java_trait_constr += ");\n"
+                java_trait_constr += "\t\t\t\tReference.reachabilityFence(arg);\n"
                 if fn_line.ret_ty_info.java_ty != "void":
                     if fn_line.ret_ty_info.from_hu_conv is not None:
                         java_trait_constr = java_trait_constr + "\t\t\t\t" + fn_line.ret_ty_info.java_ty + " result = " + fn_line.ret_ty_info.from_hu_conv[0] + ";\n"
@@ -770,17 +1052,21 @@ import javax.annotation.Nullable;
                 java_trait_constr = java_trait_constr + ", " + var.arg_name
             else:
                 java_trait_constr += ", " + var[1] + ".new_impl(" + var[1] + "_impl"
+                suptrait_constr = ""
                 for suparg in var[2]:
                     if isinstance(suparg, ConvInfo):
-                        java_trait_constr += ", " + suparg.arg_name
+                        suptrait_constr += ", " + suparg.arg_name
                     else:
-                        java_trait_constr += ", " + suparg[1]
-                java_trait_constr += ").bindings_instance"
+                        suptrait_constr += ", " + suparg[1] + "_impl"
+                java_trait_constr += suptrait_constr + ").bindings_instance"
                 for suparg in var[2]:
                     if isinstance(suparg, ConvInfo):
                         java_trait_constr += ", " + suparg.arg_name
                     else:
-                        java_trait_constr += ", " + suparg[1]
+                        java_trait_constr += ", " + suparg[1] + ".new_impl("
+                        # Blindly assume that we can just strip the first arg to build the args for the supertrait
+                        java_trait_constr += suptrait_constr.split(", ", 1)[1]
+                        java_trait_constr += ").bindings_instance"
         out_java_trait = out_java_trait + "\t}\n"
         out_java_trait = out_java_trait + java_trait_constr + ");\n\t\treturn impl_holder.held;\n\t}\n"
 
@@ -848,7 +1134,10 @@ import javax.annotation.Nullable;
                 if fn_line.ret_ty_info.c_ty.endswith("Array"):
                     out_c = out_c + "\t" + fn_line.ret_ty_info.c_ty + " ret = (*env)->CallObjectMethod(env, obj, j_calls->" + fn_line.fn_name + "_meth"
                 elif fn_line.ret_ty_info.c_ty == "void":
-                    out_c += "\t(*env)->Call" + fn_line.ret_ty_info.java_ty.title() + "Method(env, obj, j_calls->" + fn_line.fn_name + "_meth"
+                    out_c += "\t(*env)->CallVoidMethod(env, obj, j_calls->" + fn_line.fn_name + "_meth"
+                elif fn_line.ret_ty_info.java_hu_ty == "String" or "org/ldk/enums" in fn_line.ret_ty_info.java_fn_ty_arg:
+                    # Manually write out String methods as they're just an Object
+                    out_c += "\t" + fn_line.ret_ty_info.c_ty + " ret = (*env)->CallObjectMethod(env, obj, j_calls->" + fn_line.fn_name + "_meth"
                 elif not fn_line.ret_ty_info.passed_as_ptr:
                     out_c += "\t" + fn_line.ret_ty_info.c_ty + " ret = (*env)->Call" + fn_line.ret_ty_info.java_ty.title() + "Method(env, obj, j_calls->" + fn_line.fn_name + "_meth"
                 else:
@@ -861,7 +1150,7 @@ import javax.annotation.Nullable;
                         out_c = out_c + ", " + arg_info.arg_name
                 out_c = out_c + ");\n"
 
-                out_c += "\tif ((*env)->ExceptionCheck(env)) {\n"
+                out_c += "\tif (UNLIKELY((*env)->ExceptionCheck(env))) {\n"
                 out_c += "\t\t(*env)->ExceptionDescribe(env);\n"
                 out_c += "\t\t(*env)->FatalError(env, \"A call to " + fn_line.fn_name + " in " + struct_name + " from rust threw an exception.\");\n"
                 out_c += "\t}\n"
@@ -886,9 +1175,9 @@ import javax.annotation.Nullable;
             out_c = out_c + "static void " + struct_name + "_JCalls_cloned(" + struct_name + "* new_obj) {\n"
             out_c = out_c + "\t" + struct_name + "_JCalls *j_calls = (" + struct_name + "_JCalls*) new_obj->this_arg;\n"
             out_c = out_c + "\tatomic_fetch_add_explicit(&j_calls->refcnt, 1, memory_order_release);\n"
-            for var in field_vars:
+            for var in flattened_field_vars:
                 if not isinstance(var, ConvInfo):
-                    out_c = out_c + "\tatomic_fetch_add_explicit(&j_calls->" + var[1] + "->refcnt, 1, memory_order_release);\n"
+                    out_c = out_c + "\tatomic_fetch_add_explicit(&j_calls->" + var[2].replace(".", "->") + "->refcnt, 1, memory_order_release);\n"
             out_c = out_c + "}\n"
 
         out_c = out_c + "static inline " + struct_name + " " + struct_name + "_init (" + self.c_fn_args_pfx + ", jobject o"
@@ -942,7 +1231,7 @@ import javax.annotation.Nullable;
         out_c = out_c + "\t};\n"
         for var in flattened_field_vars:
             if not isinstance(var, ConvInfo):
-                out_c = out_c + "\tcalls->" + var[1] + " = ret." + var[1] + ".this_arg;\n"
+                out_c = out_c + "\tcalls->" + var[1] + " = ret." + var[2] + ".this_arg;\n"
         out_c = out_c + "\treturn ret;\n"
         out_c = out_c + "}\n"
 
@@ -961,7 +1250,7 @@ import javax.annotation.Nullable;
             else:
                 out_c = out_c + ", " + var[1]
         out_c = out_c + ");\n"
-        out_c = out_c + "\treturn (uint64_t)res_ptr;\n"
+        out_c = out_c + "\treturn tag_ptr(res_ptr, true);\n"
         out_c = out_c + "}\n"
 
         for var in flattened_field_vars:
@@ -972,7 +1261,7 @@ import javax.annotation.Nullable;
                 underscore_name = ''.join('_' + c.lower() if c.isupper() else c for c in var[1]).strip('_')
                 out_java_trait += "\tpublic " + var[1] + " get_" + underscore_name + "() {\n"
                 out_java_trait += "\t\t" + var[1] + " res = new " + var[1] + "(null, bindings." + struct_name + "_get_" + var[1] + "(this.ptr));\n"
-                out_java_trait += "\t\tthis.ptrs_to.add(res);\n"
+                out_java_trait += "\t\tres.ptrs_to.add(this);\n"
                 out_java_trait += "\t\treturn res;\n"
                 out_java_trait += "\t}\n"
                 out_java_trait += "\n"
@@ -980,10 +1269,8 @@ import javax.annotation.Nullable;
                 out_java += "\tpublic static native long " + struct_name + "_get_" + var[1] + "(long arg);\n"
 
                 out_c += "JNIEXPORT int64_t JNICALL Java_org_ldk_impl_bindings_" + struct_name + "_1get_1" + var[1] + "(JNIEnv *env, jclass clz, int64_t arg) {\n"
-                out_c += "\t" + struct_name + " *inp = (" + struct_name + " *)(arg & ~1);\n"
-                out_c += "\tuint64_t res_ptr = (uint64_t)&inp->" + var[1] + ";\n"
-                out_c += "\tDO_ASSERT((res_ptr & 1) == 0);\n"
-                out_c += "\treturn (int64_t)(res_ptr | 1);\n"
+                out_c += "\t" + struct_name + " *inp = (" + struct_name + " *)untag_ptr(arg);\n"
+                out_c += "\treturn tag_ptr(&inp->" + var[2] + ", false);\n"
                 out_c += "}\n"
 
         return (out_java, out_java_trait, out_c)
@@ -1020,7 +1307,9 @@ import javax.annotation.Nullable;
         out_java +=  ("\t\tprivate " + struct_name + "() {}\n")
         for var in variant_list:
             out_java +=  ("\t\tpublic final static class " + var.var_name + " extends " + struct_name + " {\n")
-            java_hu_subclasses = java_hu_subclasses + "\tpublic final static class " + var.var_name + " extends " + java_hu_type + " {\n"
+            if var.var_docs is not None:
+                java_hu_subclasses += "\t/**\n\t * " + var.var_docs.replace("\n", "\n\t * ") + "\n\t */\n"
+            java_hu_subclasses += "\tpublic final static class " + var.var_name + " extends " + java_hu_type + " {\n"
             out_java_enum += ("\t\tif (raw_val.getClass() == bindings." + struct_name + "." + var.var_name + ".class) {\n")
             out_java_enum += ("\t\t\treturn new " + var.var_name + "(ptr, (bindings." + struct_name + "." + var.var_name + ")raw_val);\n")
             init_meth_jty_str = ""
@@ -1031,20 +1320,19 @@ import javax.annotation.Nullable;
                 if idx > 0:
                     init_meth_params = init_meth_params + ", "
 
-                if field_ty.java_hu_ty == var.var_name:
-                    field_path = field_ty.java_fn_ty_arg.strip("L;").replace("/", ".")
-                    out_java += "\t\t\tpublic " + field_path + " " + field_ty.arg_name + ";\n"
-                    java_hu_subclasses = java_hu_subclasses + "\t\tpublic final " + field_path + " " + field_ty.arg_name + ";\n"
-                    init_meth_params = init_meth_params + field_path + " " + field_ty.arg_name
-                else:
-                    out_java += "\t\t\tpublic " + field_ty.java_ty + " " + field_ty.arg_name + ";\n"
-                    if field_docs is not None:
-                        java_hu_subclasses += "\t\t/**\n\t\t * " + field_docs.replace("\n", "\n\t\t * ") + "\n\t\t*/\n"
-                    java_hu_subclasses += "\t\t"
-                    if field_ty.nullable:
-                        java_hu_subclasses += "@Nullable "
-                    java_hu_subclasses += "public final " + field_ty.java_hu_ty + " " + field_ty.arg_name + ";\n"
-                    init_meth_params = init_meth_params + field_ty.java_ty + " " + field_ty.arg_name
+                java_ty = field_ty.java_ty
+                if field_ty.java_fn_ty_arg.startswith("L") and field_ty.java_fn_ty_arg.endswith(";"):
+                    # If this is a simple enum, we have to reference it in the low-level bindings differently:
+                    java_ty = field_ty.java_fn_ty_arg.strip("L;").replace("/", ".")
+                out_java += "\t\t\tpublic " + java_ty + " " + field_ty.arg_name + ";\n"
+                if field_docs is not None:
+                    java_hu_subclasses += "\t\t/**\n\t\t * " + field_docs.replace("\n", "\n\t\t * ") + "\n\t\t*/\n"
+                java_hu_subclasses += "\t\t"
+                if field_ty.nullable:
+                    java_hu_subclasses += "@Nullable "
+                java_hu_subclasses += "public final " + self.fully_qualified_hu_ty_path(field_ty) + " " + field_ty.arg_name + ";\n"
+                init_meth_params = init_meth_params + java_ty + " " + field_ty.arg_name
+
                 init_meth_body = init_meth_body + "this." + field_ty.arg_name + " = " + field_ty.arg_name + "; "
                 if field_ty.to_hu_conv is not None:
                     hu_conv_body = hu_conv_body + "\t\t\t" + field_ty.java_ty + " " + field_ty.arg_name + " = obj." + field_ty.arg_name + ";\n"
@@ -1072,7 +1360,7 @@ import javax.annotation.Nullable;
         out_c += (self.c_complex_enum_pfx(struct_name, [x.var_name for x in variant_list], init_meth_jty_strs))
 
         out_c += (self.c_fn_ty_pfx + self.c_complex_enum_pass_ty(struct_name) + " " + self.c_fn_name_define_pfx(struct_name + "_ref_from_ptr", True) + self.ptr_c_ty + " ptr) {\n")
-        out_c += ("\t" + struct_name + " *obj = (" + struct_name + "*)(ptr & ~1);\n")
+        out_c += ("\t" + struct_name + " *obj = (" + struct_name + "*)untag_ptr(ptr);\n")
         out_c += ("\tswitch(obj->tag) {\n")
         for var in variant_list:
             out_c += ("\t\tcase " + struct_name + "_" + var.var_name + ": {\n")
@@ -1102,12 +1390,13 @@ import javax.annotation.Nullable;
         out_opaque_struct_human += self.hu_struct_file_prefix
         out_opaque_struct_human += "\n/**\n * " + struct_doc_comment.replace("\n", "\n * ") + "\n */\n"
         out_opaque_struct_human += "@SuppressWarnings(\"unchecked\") // We correctly assign various generic arrays\n"
-        out_opaque_struct_human += ("public class " + struct_name.replace("LDK","") + " extends CommonBase")
-        if struct_name.startswith("LDKLocked"):
+        hu_name = struct_name.replace("LDKC2Tuple", "TwoTuple").replace("LDKC3Tuple", "ThreeTuple").replace("LDK", "")
+        out_opaque_struct_human += ("public class " + hu_name + " extends CommonBase")
+        if struct_name.startswith("LDKLocked") or struct_name.startswith("LDKReadOnly"):
             out_opaque_struct_human += (" implements AutoCloseable")
         out_opaque_struct_human += (" {\n")
-        out_opaque_struct_human += ("\t" + struct_name.replace("LDK", "") + "(Object _dummy, long ptr) { super(ptr); }\n")
-        if struct_name.startswith("LDKLocked"):
+        out_opaque_struct_human += ("\t" + hu_name + "(Object _dummy, long ptr) { super(ptr); }\n")
+        if struct_name.startswith("LDKLocked") or struct_name.startswith("LDKReadOnly"):
             out_opaque_struct_human += ("\t@Override public void close() {\n")
         else:
             out_opaque_struct_human += ("\t@Override @SuppressWarnings(\"deprecation\")\n")
@@ -1117,8 +1406,62 @@ import javax.annotation.Nullable;
         out_opaque_struct_human += ("\t}\n\n")
         return out_opaque_struct_human
 
+    def map_tuple(self, struct_name):
+        return self.map_opaque_struct(struct_name, "A Tuple")
+
+    def map_result(self, struct_name, res_map, err_map):
+        human_ty = struct_name.replace("LDKCResult", "Result")
+        java_hu_struct = ""
+        java_hu_struct += self.hu_struct_file_prefix
+        java_hu_struct += "public class " + human_ty + " extends CommonBase {\n"
+        java_hu_struct += "\tprivate " + human_ty + "(Object _dummy, long ptr) { super(ptr); }\n"
+        java_hu_struct += "\tprotected void finalize() throws Throwable {\n"
+        java_hu_struct += "\t\tif (ptr != 0) { bindings." + struct_name.replace("LDK","") + "_free(ptr); } super.finalize();\n"
+        java_hu_struct += "\t}\n\n"
+        java_hu_struct += "\tstatic " + human_ty + " constr_from_ptr(long ptr) {\n"
+        java_hu_struct += "\t\tif (bindings." + struct_name.replace("LDK", "") + "_is_ok(ptr)) {\n"
+        java_hu_struct += "\t\t\treturn new " + human_ty + "_OK(null, ptr);\n"
+        java_hu_struct += "\t\t} else {\n"
+        java_hu_struct += "\t\t\treturn new " + human_ty + "_Err(null, ptr);\n"
+        java_hu_struct += "\t\t}\n"
+        java_hu_struct += "\t}\n"
+
+        java_hu_struct += "\tpublic static final class " + human_ty + "_OK extends " + human_ty + " {\n"
+
+        if res_map.java_hu_ty != "void":
+            java_hu_struct += "\t\tpublic final " + res_map.java_hu_ty + " res;\n"
+        java_hu_struct += "\t\tprivate " + human_ty + "_OK(Object _dummy, long ptr) {\n"
+        java_hu_struct += "\t\t\tsuper(_dummy, ptr);\n"
+        if res_map.java_hu_ty == "void":
+            pass
+        elif res_map.to_hu_conv is not None:
+            java_hu_struct += "\t\t\t" + res_map.java_ty + " res = bindings." + struct_name.replace("LDK", "") + "_get_ok(ptr);\n"
+            java_hu_struct += "\t\t\t" + res_map.to_hu_conv.replace("\n", "\n\t\t\t")
+            java_hu_struct += "\n\t\t\tthis.res = " + res_map.to_hu_conv_name + ";\n"
+        else:
+            java_hu_struct += "\t\t\tthis.res = bindings." + struct_name.replace("LDK", "") + "_get_ok(ptr);\n"
+        java_hu_struct += "\t\t}\n"
+        java_hu_struct += "\t}\n\n"
+
+        java_hu_struct += "\tpublic static final class " + human_ty + "_Err extends " + human_ty + " {\n"
+        if err_map.java_hu_ty != "void":
+            java_hu_struct += "\t\tpublic final " + err_map.java_hu_ty + " err;\n"
+        java_hu_struct += "\t\tprivate " + human_ty + "_Err(Object _dummy, long ptr) {\n"
+        java_hu_struct += "\t\t\tsuper(_dummy, ptr);\n"
+        if err_map.java_hu_ty == "void":
+            pass
+        elif err_map.to_hu_conv is not None:
+            java_hu_struct += "\t\t\t" + err_map.java_ty + " err = bindings." + struct_name.replace("LDK", "") + "_get_err(ptr);\n"
+            java_hu_struct += "\t\t\t" + err_map.to_hu_conv.replace("\n", "\n\t\t\t")
+            java_hu_struct += "\n\t\t\tthis.err = " + err_map.to_hu_conv_name + ";\n"
+        else:
+            java_hu_struct += "\t\t\tthis.err = bindings." + struct_name.replace("LDK", "") + "_get_err(ptr);\n"
+        java_hu_struct += "\t\t}\n"
+
+        java_hu_struct += "\t}\n\n"
+        return java_hu_struct
 
-    def map_function(self, argument_types, c_call_string, method_name, return_type_info, struct_meth, default_constructor_args, takes_self, takes_self_as_ref, args_known, type_mapping_generator, doc_comment):
+    def map_function(self, argument_types, c_call_string, method_name, meth_n, return_type_info, struct_meth, default_constructor_args, takes_self, takes_self_as_ref, args_known, type_mapping_generator, doc_comment):
         out_java = ""
         out_c = ""
         out_java_struct = None
@@ -1142,10 +1485,10 @@ import javax.annotation.Nullable;
                 out_java += (arg_conv_info.java_ty + " " + arg_conv_info.arg_name)
 
         out_java_struct = ""
+        extra_java_struct_out = ""
         if not args_known:
             out_java_struct += ("\t// Skipped " + method_name + "\n")
         else:
-            meth_n = method_name[len(struct_meth) + 1 if len(struct_meth) != 0 else 0:].strip("_")
             if doc_comment is not None:
                 out_java_struct += "\t/**\n\t * " + doc_comment.replace("\n", "\n\t * ") + "\n\t */\n"
             if return_type_info.nullable:
@@ -1157,7 +1500,20 @@ import javax.annotation.Nullable;
                     out_java_struct += "\tpublic static " + return_type_info.java_hu_ty + " with_default("
                 else:
                     out_java_struct += "\tpublic static " + return_type_info.java_hu_ty + " " + meth_n + "("
+            elif meth_n == "clone_ptr" or (struct_meth.startswith("LDKCResult") and (meth_n == "get_ok" or meth_n == "get_err")):
+                out_java_struct += ("\t" + return_type_info.java_hu_ty + " " + meth_n + "(")
             else:
+                if meth_n == "hash" and return_type_info.java_hu_ty == "long":
+                    extra_java_struct_out = "\t@Override public int hashCode() {\n"
+                    extra_java_struct_out += "\t\treturn (int)this.hash();\n"
+                    extra_java_struct_out += "\t}\n"
+                elif meth_n == "eq" and return_type_info.java_hu_ty == "boolean":
+                    extra_java_struct_out = "\t@Override public boolean equals(Object o) {\n"
+                    extra_java_struct_out += "\t\tif (!(o instanceof " + struct_meth + ")) return false;\n"
+                    extra_java_struct_out += "\t\treturn this.eq((" + struct_meth + ")o);\n"
+                    extra_java_struct_out += "\t}\n"
+                if meth_n == "wait":
+                    meth_n = "wait_indefinite"
                 out_java_struct += ("\tpublic " + return_type_info.java_hu_ty + " " + meth_n + "(")
             for idx, arg in enumerate(argument_types):
                 if idx != 0:
@@ -1176,9 +1532,7 @@ import javax.annotation.Nullable;
                     else:
                         if arg.nullable:
                             out_java_struct += "@Nullable "
-                        ty_string = arg.java_hu_ty
-                        if arg.java_fn_ty_arg[0] == "L" and arg.java_fn_ty_arg[len(arg.java_fn_ty_arg) - 1] == ";":
-                            ty_string = arg.java_fn_ty_arg.strip("L;").replace("/", ".")
+                        ty_string = self.fully_qualified_hu_ty_path(arg)
                         out_java_struct += ty_string + " " + arg.arg_name
         out_java += (");\n")
         out_c += (") {\n")
@@ -1245,8 +1599,47 @@ import javax.annotation.Nullable;
                 else:
                     out_java_struct += (info.arg_name)
             out_java_struct += (");\n")
+
+            # This is completely nuts. The OpenJDK JRE JIT will optimize out a object which is on
+            # the stack, calling its finalizer immediately even if member methods are *actively
+            # executing* on the same object, as long as said object is on the stack. There is no
+            # concrete specification for when the optimizer is allowed to do this, and when it is
+            # not, so there is absolutely no way to be certain that this fix suffices.
+            #
+            # Instead, the "Java Language Specification" says only that an object is reachable
+            # (i.e. will not yet be finalized) if it "can be accessed in any potential continuing
+            # computation from any live thread". To any sensible reader this would mean actively
+            # executing a member function on an object would make it not eligible for finalization.
+            # But, no, dear reader, this statement does not say that. Well, okay, it says that,
+            # very explicitly in fact, but those are just, like, words, man.
+            #
+            # In the seemingly non-normative text further down, a few examples of things the
+            # optimizer can do are given, including "if the values in an object's fields are
+            # stored in registers[, t]he may then access the registers instead of the object, and
+            # never access the object again[, implying] that the object is garbage". This appears
+            # to fully contradict both the above statement, the API documentation in java.lang.ref
+            # regarding when a reference is "strongly reachable", and basic common sense. There is
+            # no concrete set of limitations stated, however, seemingly implying the JIT could
+            # decide your code would run faster by simply garbage collecting everything
+            # immediately, ensuring your code finishes soon, just by SEGFAULT. Thus, we're really
+            # entirely flying blind here. We add some fences and hope that its sufficient, but
+            # with no specification to rely on, we cannot be certain of anything.
+            #
+            # TL;DR: The Java Language "Specification" provides no real guarantees on when an
+            # object will be considered available for garbage collection once the JIT kicks in, so
+            # we put in some fences and hope to god the JIT doesn't get smarter/more broken.
+            for idx, info in enumerate(argument_types):
+                if idx == 0 and takes_self:
+                    out_java_struct += ("\t\tReference.reachabilityFence(this);\n")
+                elif info.arg_name in default_constructor_args:
+                    for explode_idx, explode_arg in enumerate(default_constructor_args[info.arg_name]):
+                        expl_arg_name = info.arg_name + "_" + explode_arg.arg_name
+                        out_java_struct += ("\t\tReference.reachabilityFence(" + expl_arg_name + ");\n")
+                elif info.c_ty != "void":
+                    out_java_struct += ("\t\tReference.reachabilityFence(" + info.arg_name + ");\n")
+
             if return_type_info.java_ty == "long" and return_type_info.java_hu_ty != "long":
-                out_java_struct += "\t\tif (ret < 1024) { return null; }\n"
+                out_java_struct += "\t\tif (ret >= 0 && ret <= 4096) { return null; }\n"
 
             if return_type_info.to_hu_conv is not None:
                 if not takes_self:
@@ -1280,4 +1673,7 @@ import javax.annotation.Nullable;
                 out_java_struct += ("\t\treturn ret;\n")
             out_java_struct += ("\t}\n\n")
 
-        return (out_java, out_c, out_java_struct)
+        return (out_java, out_c, out_java_struct + extra_java_struct_out)
+
+    def cleanup(self):
+        pass