forked from lightningdevkit/ldk-garbagecollected
-
Notifications
You must be signed in to change notification settings - Fork 1
/
python_strings.py
1588 lines (1420 loc) · 69.8 KB
/
python_strings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from bindingstypes import ConvInfo
from enum import Enum
def first_to_lower(string: str) -> str:
first = string[0]
return first.lower() + string[1:]
class Target(Enum):
PYTHON = 1,
class Consts:
def __init__(self, DEBUG: bool, target: Target, outdir: str, **kwargs):
self.outdir = outdir
self.struct_file_suffixes = {}
self.function_ptr_counter = 0
self.function_ptrs = {}
self.c_type_map = dict(
bool = ['bool', 'bool', 'XXX'],
uint8_t = ['int', 'int', 'Uint8Array'],
uint16_t = ['int', 'int', 'Uint16Array'],
uint32_t = ['int', 'int', 'Uint32Array'],
uint64_t = ['int', 'int', 'BigUint64Array'],
)
self.java_type_map = dict(
String = "number"
)
self.java_hu_type_map = dict(
String = "string"
)
self.to_hu_conv_templates = dict(
ptr = 'const {var_name}_hu_conv: {human_type} = new {human_type}(null, {var_name});',
default = 'const {var_name}_hu_conv: {human_type} = new {human_type}(null, {var_name});',
)
self.bindings_header = """
import * as version from './version.mjs';
import { UInt5, WitnessVersion } from './structs/CommonBase.mjs';
const imports: any = {};
imports.env = {};
var js_objs: Array<WeakRef<object>> = [];
var js_invoke: Function;
var getRandomValues: Function;
imports.wasi_snapshot_preview1 = {
"fd_write": (fd: number, iovec_array_ptr: number, iovec_array_len: number, bytes_written_ptr: number) => {
// This should generally only be used to print panic messages
const ptr_len_view = new Uint32Array(wasm.memory.buffer, iovec_array_ptr, iovec_array_len * 2);
var bytes_written = 0;
for (var i = 0; i < iovec_array_len; i++) {
const bytes_view = new Uint8Array(wasm.memory.buffer, ptr_len_view[i*2], ptr_len_view[i*2+1]);
console.log("[fd " + fd + "]: " + String.fromCharCode(...bytes_view));
bytes_written += ptr_len_view[i*2+1]!;
}
const written_view = new Uint32Array(wasm.memory.buffer, bytes_written_ptr, 1);
written_view[0] = bytes_written;
return 0;
},
"fd_close": (_fd: number) => {
// This is not generally called, but may be referenced in debug builds
console.log("wasi_snapshot_preview1:fd_close");
return 58; // Not Supported
},
"fd_seek": (_fd: number, _offset: bigint, _whence: number, _new_offset: number) => {
// This is not generally called, but may be referenced in debug builds
console.log("wasi_snapshot_preview1:fd_seek");
return 58; // Not Supported
},
"random_get": (buf_ptr: number, buf_len: number) => {
const buf = new Uint8Array(wasm.memory.buffer, buf_ptr, buf_len);
getRandomValues(buf);
return 0;
},
"environ_sizes_get": (environ_var_count_ptr: number, environ_len_ptr: number) => {
// This is called before fd_write to format + print panic messages
const out_count_view = new Uint32Array(wasm.memory.buffer, environ_var_count_ptr, 1);
out_count_view[0] = 0;
const out_len_view = new Uint32Array(wasm.memory.buffer, environ_len_ptr, 1);
out_len_view[0] = 0;
return 0;
},
"environ_get": (_environ_ptr: number, _environ_buf_ptr: number) => {
// This is called before fd_write to format + print panic messages,
// but only if we have variables in environ_sizes_get, so shouldn't ever actually happen!
console.log("wasi_snapshot_preview1:environ_get");
return 58; // Note supported - we said there were 0 environment entries!
},
"proc_exit" : () => {
console.log("wasi_snapshot_preview1:proc_exit");
},
};
var wasm: any = null;
let isWasmInitialized: boolean = false;
async function finishInitializeWasm(wasmInstance: WebAssembly.Instance) {
if (typeof crypto === "undefined") {
var crypto_import = (await import('crypto')).webcrypto;
getRandomValues = crypto_import.getRandomValues.bind(crypto_import);
} else {
getRandomValues = crypto.getRandomValues.bind(crypto);
}
wasm = wasmInstance.exports;
if (!wasm.test_bigint_pass_deadbeef0badf00d(BigInt("0xdeadbeef0badf00d"))) {
throw new Error(\"Currently need BigInt-as-u64 support, try ----experimental-wasm-bigint");
}
if (decodeString(wasm.TS_get_lib_version_string()) !== version.get_ldk_java_bindings_version())
throw new Error(\"Compiled LDK library and LDK class files do not match\");
// Fetching the LDK versions from C also checks that the header and binaries match
const c_bindings_ver: number = wasm.TS_get_ldk_c_bindings_version();
const ldk_ver: number = wasm.TS_get_ldk_version();
if (c_bindings_ver == 0)
throw new Error(\"LDK version did not match the header we built against\");
if (ldk_ver == 0)
throw new Error(\"LDK C bindings version did not match the header we built against\");
const c_bindings_version: string = decodeString(c_bindings_ver)
const ldk_version: string = decodeString(ldk_ver);
console.log(\"Loaded LDK-Java Bindings with LDK \" + ldk_version + \" and LDK-C-Bindings \" + c_bindings_version);
isWasmInitialized = true;
}
const fn_list = ["uuuuuu", "buuuuu", "bbuuuu", "bbbuuu", "bbbbuu",
"bbbbbb", "ubuubu", "ubuuuu", "ubbuuu", "uubuuu", "uububu", "ububuu"];
/* @internal */
export async function initializeWasmFromUint8Array(wasmBinary: Uint8Array) {
for (const fn of fn_list) { imports.env["js_invoke_function_" + fn] = js_invoke; }
const { instance: wasmInstance } = await WebAssembly.instantiate(wasmBinary, imports);
await finishInitializeWasm(wasmInstance);
}
/* @internal */
export async function initializeWasmFetch(uri: string) {
for (const fn of fn_list) { imports.env["js_invoke_function_" + fn] = js_invoke; }
const stream = fetch(uri);
const { instance: wasmInstance } = await WebAssembly.instantiateStreaming(stream, imports);
await finishInitializeWasm(wasmInstance);
}"""
self.bindings_header += """
// WASM CODEC
/* @internal */
export function uint5ArrToBytes(inputArray: Array<UInt5>): Uint8Array {
const arr = new Uint8Array(inputArray.length);
for (var i = 0; i < inputArray.length; i++) {
arr[i] = inputArray[i]!.getVal();
}
return arr;
}
/* @internal */
export function WitnessVersionArrToBytes(inputArray: Array<WitnessVersion>): Uint8Array {
const arr = new Uint8Array(inputArray.length);
for (var i = 0; i < inputArray.length; i++) {
arr[i] = inputArray[i]!.getVal();
}
return arr;
}
/* @internal */
export function encodeUint128 (inputVal: bigint): number {
if (inputVal >= 0x10000000000000000000000000000000n) throw "U128s cannot exceed 128 bits";
const cArrayPointer = wasm.TS_malloc(16 + 8);
const arrayLengthView = new BigUint64Array(wasm.memory.buffer, cArrayPointer, 1);
arrayLengthView[0] = BigInt(16);
const arrayMemoryView = new Uint8Array(wasm.memory.buffer, cArrayPointer + 8, 16);
for (var i = 0; i < 16; i++) arrayMemoryView[i] = Number((inputVal >> BigInt(i)*8n) & 0xffn);
return cArrayPointer;
}
/* @internal */
export function encodeUint8Array (inputArray: Uint8Array|null): number {
if (inputArray == null) return 0;
const cArrayPointer = wasm.TS_malloc(inputArray.length + 8);
const arrayLengthView = new BigUint64Array(wasm.memory.buffer, cArrayPointer, 1);
arrayLengthView[0] = BigInt(inputArray.length);
const arrayMemoryView = new Uint8Array(wasm.memory.buffer, cArrayPointer + 8, inputArray.length);
arrayMemoryView.set(inputArray);
return cArrayPointer;
}
/* @internal */
export function encodeUint32Array (inputArray: Uint32Array|Array<number>|null): number {
if (inputArray == null) return 0;
const cArrayPointer = wasm.TS_malloc((inputArray.length + 2) * 4);
const arrayLengthView = new BigUint64Array(wasm.memory.buffer, cArrayPointer, 1);
arrayLengthView[0] = BigInt(inputArray.length);
const arrayMemoryView = new Uint32Array(wasm.memory.buffer, cArrayPointer + 8, inputArray.length);
arrayMemoryView.set(inputArray);
return cArrayPointer;
}
/* @internal */
export function encodeUint64Array (inputArray: BigUint64Array|Array<bigint>|null): number {
if (inputArray == null) return 0;
const cArrayPointer = wasm.TS_malloc((inputArray.length + 1) * 8);
const arrayMemoryView = new BigUint64Array(wasm.memory.buffer, cArrayPointer, inputArray.length + 1);
arrayMemoryView[0] = BigInt(inputArray.length);
arrayMemoryView.set(inputArray, 1);
return cArrayPointer;
}
/* @internal */
export function check_arr_len(arr: Uint8Array|null, len: number): Uint8Array|null {
if (arr !== null && arr.length != len) { throw new Error("Expected array of length " + len + " got " + arr.length); }
return arr;
}
/* @internal */
export function getArrayLength(arrayPointer: number): number {
const arraySizeViewer = new BigUint64Array(wasm.memory.buffer, arrayPointer, 1);
const len = arraySizeViewer[0]!;
if (len >= (2n ** 32n)) throw new Error("Bogus Array Size");
return Number(len % (2n ** 32n));
}
/* @internal */
export function decodeUint128 (arrayPointer: number, free = true): bigint {
const arraySize = getArrayLength(arrayPointer);
if (arraySize != 16) throw "Need 16 bytes for a uint128";
const actualArrayViewer = new Uint8Array(wasm.memory.buffer, arrayPointer + 8, arraySize);
var val = 0n;
for (var i = 0; i < 16; i++) {
val <<= 8n;
val |= BigInt(actualArrayViewer[i]!);
}
if (free) {
wasm.TS_free(arrayPointer);
}
return val;
}
/* @internal */
export function decodeUint8Array (arrayPointer: number, free = true): Uint8Array {
const arraySize = getArrayLength(arrayPointer);
const actualArrayViewer = new Uint8Array(wasm.memory.buffer, arrayPointer + 8, arraySize);
// Clone the contents, TODO: In the future we should wrap the Viewer in a class that
// will free the underlying memory when it becomes unreachable instead of copying here.
// Note that doing so may have edge-case interactions with memory resizing (invalidating the buffer).
const actualArray = actualArrayViewer.slice(0, arraySize);
if (free) {
wasm.TS_free(arrayPointer);
}
return actualArray;
}
const decodeUint32Array = (arrayPointer: number, free = true) => {
const arraySize = getArrayLength(arrayPointer);
const actualArrayViewer = new Uint32Array(
wasm.memory.buffer, // value
arrayPointer + 8, // offset (ignoring length bytes)
arraySize // uint32 count
);
// Clone the contents, TODO: In the future we should wrap the Viewer in a class that
// will free the underlying memory when it becomes unreachable instead of copying here.
const actualArray = actualArrayViewer.slice(0, arraySize);
if (free) {
wasm.TS_free(arrayPointer);
}
return actualArray;
}
/* @internal */
export function decodeUint64Array (arrayPointer: number, free = true): bigint[] {
const arraySize = getArrayLength(arrayPointer);
const actualArrayViewer = new BigUint64Array(
wasm.memory.buffer, // value
arrayPointer + 8, // offset (ignoring length bytes)
arraySize // uint32 count
);
// Clone the contents, TODO: In the future we should wrap the Viewer in a class that
// will free the underlying memory when it becomes unreachable instead of copying here.
const actualArray = new Array(arraySize);
for (var i = 0; i < arraySize; i++) actualArray[i] = actualArrayViewer[i];
if (free) {
wasm.TS_free(arrayPointer);
}
return actualArray;
}
export function freeWasmMemory(pointer: number) { wasm.TS_free(pointer); }
/* @internal */
export function getU64ArrayElem(arrayPointer: number, idx: number): bigint {
const actualArrayViewer = new BigUint64Array(wasm.memory.buffer, arrayPointer + 8, idx + 1);
return actualArrayViewer[idx]!;
}
/* @internal */
export function getU32ArrayElem(arrayPointer: number, idx: number): number {
const actualArrayViewer = new Uint32Array(wasm.memory.buffer, arrayPointer + 8, idx + 1);
return actualArrayViewer[idx]!;
}
/* @internal */
export function getU8ArrayElem(arrayPointer: number, idx: number): number {
const actualArrayViewer = new Uint8Array(wasm.memory.buffer, arrayPointer + 8, idx + 1);
return actualArrayViewer[idx]!;
}
/* @internal */
export function encodeString(str: string): number {
const charArray = new TextEncoder().encode(str);
return encodeUint8Array(charArray);
}
/* @internal */
export function decodeString(stringPointer: number, free = true): string {
const arraySize = getArrayLength(stringPointer);
const memoryView = new Uint8Array(wasm.memory.buffer, stringPointer + 8, arraySize);
const result = new TextDecoder("utf-8").decode(memoryView);
if (free) {
wasm.TS_free(stringPointer);
}
return result;
}
"""
if DEBUG:
self.bindings_header += """
/* @internal */
export function getRemainingAllocationCount(): number {
return wasm.TS_allocs_remaining();
}
/* @internal */
export function debugPrintRemainingAllocs() {
wasm.TS_print_leaks();
}
"""
else:
self.bindings_header += "\n/* @internal */ export function getRemainingAllocationCount(): number { return 0; }\n"
self.bindings_header += "/* @internal */ export function debugPrintRemainingAllocs() { }\n"
with open(outdir + "/index.py", 'a') as index:
index.write("""import { initializeWasmFetch, initializeWasmFromUint8Array } from './bindings.mjs';
/** Initializes the WASM backend by calling `fetch()` on the given URI - Browser only */
export async function initializeWasmWebFetch(uri: string) {
await initializeWasmFetch(uri);
}
/** Initializes the WASM backend given a Uint8Array of the .wasm binary file - Browser or Node.JS */
export async function initializeWasmFromBinary(bin: Uint8Array) {
await initializeWasmFromUint8Array(bin);
}
""")
self.bindings_version_file = """export function get_ldk_java_bindings_version(): String {
return "<git_version_ldk_garbagecollected>";
}"""
self.common_base = """
function freer(f: () => void) { f() }
const finalizer = new FinalizationRegistry(freer);
function get_freeer(ptr: bigint, free_fn: (ptr: bigint) => void) {
return () => {
free_fn(ptr);
}
}
export class UInt5 {
public constructor(private val: number) {
if (val > 32 || val < 0) throw new Error("UInt5 value is out of range");
}
public getVal(): number {
return this.val;
}
}
export class WitnessVersion {
public constructor(private val: number) {
if (val > 16 || val < 0) throw new Error("WitnessVersion value is out of range");
}
public getVal(): number {
return this.val;
}
}
export class UnqualifiedError {
public constructor(_val: number) {}
}
"""
self.txout_defn = """export class TxOut extends CommonBase {
/** The script_pubkey in this output */
public script_pubkey: Uint8Array;
/** The value, in satoshis, of this output */
public value: bigint;
/* @internal */
public constructor(_dummy: null, ptr: bigint) {
super(ptr, bindings.TxOut_free);
this.script_pubkey = bindings.decodeUint8Array(bindings.TxOut_get_script_pubkey(ptr));
this.value = bindings.TxOut_get_value(ptr);
}
public static constructor_new(value: bigint, script_pubkey: Uint8Array): TxOut {
return new TxOut(null, bindings.TxOut_new(bindings.encodeUint8Array(script_pubkey), value));
}
}"""
self.obj_defined(["TxOut"], "structs")
self.scalar_defn = """export class BigEndianScalar extends CommonBase {
/** The bytes of the scalar value, in big endian */
public scalar_bytes: Uint8Array;
/* @internal */
public constructor(_dummy: null, ptr: bigint) {
super(ptr, bindings.BigEndianScalar_free);
this.scalar_bytes = bindings.decodeUint8Array(bindings.BigEndianScalar_get_bytes(ptr));
}
public static constructor_new(scalar_bytes: Uint8Array): BigEndianScalar {
return new BigEndianScalar(null, bindings.BigEndianScalar_new(bindings.encodeUint8Array(scalar_bytes)));
}
}"""
self.obj_defined(["BigEndianScalar"], "structs")
self.c_file_pfx = """#include "js-wasm.h"
#include <stdatomic.h>
#include <lightning.h>
// These should be provided...somehow...
void *memset(void *s, int c, size_t n);
void *memcpy(void *dest, const void *src, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
extern void __attribute__((noreturn)) abort(void);
static inline void assert(bool expression) {
if (!expression) { abort(); }
}
uint32_t __attribute__((export_name("test_bigint_pass_deadbeef0badf00d"))) test_bigint_pass_deadbeef0badf00d(uint64_t val) {
return val == 0xdeadbeef0badf00dULL;
}
"""
if not DEBUG:
self.c_file_pfx += """
void *malloc(size_t size);
void free(void *ptr);
#define MALLOC(a, _) malloc(a)
#define do_MALLOC(a, _b, _c) malloc(a)
#define FREE(p) if ((unsigned long)(p) > 4096) { free(p); }
#define DO_ASSERT(a) (void)(a)
#define CHECK(a)
#define CHECK_ACCESS(p)
#define CHECK_INNER_FIELD_ACCESS_OR_NULL(v)
"""
else:
self.c_file_pfx += """
extern int snprintf(char *str, size_t size, const char *format, ...);
typedef int32_t ssize_t;
ssize_t write(int fd, const void *buf, size_t count);
#define DEBUG_PRINT(...) do { \\
char debug_str[1024]; \\
int s_len = snprintf(debug_str, 1023, __VA_ARGS__); \\
write(2, debug_str, s_len); \\
} while (0);
// 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)
// 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.
#define BT_MAX 128
typedef struct allocation {
struct allocation* next;
void* ptr;
const char* struct_name;
int lineno;
} allocation;
static allocation* allocation_ll = NULL;
static allocation* freed_ll = NULL;
extern void* __real_malloc(size_t len);
extern void* __real_calloc(size_t nmemb, size_t len);
extern void* __real_aligned_alloc(size_t alignment, size_t size);
static void new_allocation(void* res, const char* struct_name, int lineno) {
allocation* new_alloc = __real_malloc(sizeof(allocation));
new_alloc->ptr = res;
new_alloc->struct_name = struct_name;
new_alloc->next = allocation_ll;
new_alloc->lineno = lineno;
allocation_ll = new_alloc;
}
static void* do_MALLOC(size_t len, const char* struct_name, int lineno) {
void* res = __real_malloc(len);
new_allocation(res, struct_name, lineno);
return res;
}
#define MALLOC(len, struct_name) do_MALLOC(len, struct_name, __LINE__)
void __real_free(void* ptr);
static void alloc_freed(void* ptr, int lineno) {
allocation* p = NULL;
allocation* it = allocation_ll;
while (it->ptr != ptr) {
p = it; it = it->next;
if (it == NULL) {
p = NULL;
it = freed_ll;
while (it && it->ptr != ptr) { p = it; it = it->next; }
if (it == NULL) {
DEBUG_PRINT("Tried to free unknown pointer %p at line %d.\\n", ptr, lineno);
} else {
DEBUG_PRINT("Tried to free unknown pointer %p at line %d.\\n Possibly double-free from %s, allocated on line %d.", ptr, lineno, it->struct_name, it->lineno);
}
abort();
}
}
if (p) { p->next = it->next; } else { allocation_ll = it->next; }
DO_ASSERT(it->ptr == ptr);
it->next = freed_ll;
freed_ll = it;
}
static void do_FREE(void* ptr, int lineno) {
if ((unsigned long)ptr <= 4096) return; // Rust loves to create pointers to the NULL page for dummys
alloc_freed(ptr, lineno);
__real_free(ptr);
}
#define FREE(ptr) do_FREE(ptr, __LINE__)
static void CHECK_ACCESS(const void* ptr) {
allocation* it = allocation_ll;
while (it->ptr != ptr) {
it = it->next;
if (it == NULL) {
return; // addrsan should catch malloc-unknown and print more info than we have
}
}
}
#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* __wrap_malloc(size_t len) {
void* res = __real_malloc(len);
new_allocation(res, "malloc call", 0);
return res;
}
void* __wrap_calloc(size_t nmemb, size_t len) {
void* res = __real_calloc(nmemb, len);
new_allocation(res, "calloc call", 0);
return res;
}
void* __wrap_aligned_alloc(size_t alignment, size_t size) {
void* res = __real_aligned_alloc(alignment, size);
new_allocation(res, "aligned_alloc call", 0);
return res;
}
void __wrap_free(void* ptr) {
if (ptr == NULL) return;
alloc_freed(ptr, 0);
__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, 0);
void* res = __real_realloc(ptr, len);
new_allocation(res, "realloc call", 0);
return res;
}
void __wrap_reallocarray(void* ptr, size_t new_sz) {
// Rust doesn't seem to use reallocarray currently
DO_ASSERT(false);
}
uint32_t __attribute__((export_name("TS_allocs_remaining"))) allocs_remaining() {
uint32_t count = 0;
for (allocation* a = allocation_ll; a != NULL; a = a->next) {
count++;
}
return count;
}
void __attribute__((export_name("TS_print_leaks"))) print_leaks() {
for (allocation* a = allocation_ll; a != NULL; a = a->next) {
DEBUG_PRINT("%s %p remains. Allocated on line %d\\n", a->struct_name, a->ptr, a->lineno);
}
}
"""
self.c_file_pfx = self.c_file_pfx + """
// We assume that CVec_u8Z and u8slice are the same size and layout (and thus pointers to the two can be mixed)
_Static_assert(sizeof(LDKCVec_u8Z) == sizeof(LDKu8slice), "Vec<u8> and [u8] need to have been mapped identically");
_Static_assert(offsetof(LDKCVec_u8Z, data) == offsetof(LDKu8slice, data), "Vec<u8> and [u8] need to have been mapped identically");
_Static_assert(offsetof(LDKCVec_u8Z, datalen) == offsetof(LDKu8slice, datalen), "Vec<u8> and [u8] need to have been mapped identically");
_Static_assert(sizeof(void*) == 4, "Pointers mut be 32 bits");
#define DECL_ARR_TYPE(ty, name) \\
struct name##array { \\
uint64_t arr_len; /* uint32_t would suffice but we want to align uint64_ts as well */ \\
ty elems[]; \\
}; \\
typedef struct name##array * name##Array; \\
static inline name##Array init_##name##Array(size_t arr_len, int lineno) { \\
name##Array arr = (name##Array)do_MALLOC(arr_len * sizeof(ty) + sizeof(uint64_t), #name" array init", lineno); \\
arr->arr_len = arr_len; \\
return arr; \\
}
DECL_ARR_TYPE(int64_t, int64_t);
DECL_ARR_TYPE(uint64_t, uint64_t);
DECL_ARR_TYPE(int8_t, int8_t);
DECL_ARR_TYPE(uint32_t, uint32_t);
DECL_ARR_TYPE(void*, ptr);
DECL_ARR_TYPE(char, char);
typedef charArray jstring;
static inline jstring str_ref_to_ts(const char* chars, size_t len) {
charArray arr = init_charArray(len, __LINE__);
memcpy(arr->elems, chars, len);
return arr;
}
static inline LDKStr str_ref_to_owned_c(const jstring str) {
char* newchars = MALLOC(str->arr_len + 1, "String chars");
memcpy(newchars, str->elems, str->arr_len);
newchars[str->arr_len] = 0;
LDKStr res = {
.chars = newchars,
.len = str->arr_len,
.chars_is_owned = true
};
return res;
}
typedef bool jboolean;
uint32_t __attribute__((export_name("TS_malloc"))) TS_malloc(uint32_t size) {
return (uint32_t)MALLOC(size, "JS-Called malloc");
}
void __attribute__((export_name("TS_free"))) TS_free(uint32_t ptr) {
FREE((void*)ptr);
}
jstring __attribute__((export_name("TS_get_ldk_c_bindings_version"))) TS_get_ldk_c_bindings_version() {
const char *res = check_get_ldk_bindings_version();
if (res == NULL) return NULL;
return str_ref_to_ts(res, strlen(res));
}
jstring __attribute__((export_name("TS_get_ldk_version"))) get_ldk_version() {
const char *res = check_get_ldk_version();
if (res == NULL) return NULL;
return str_ref_to_ts(res, strlen(res));
}
#include "version.c"
"""
self.c_version_file = """jstring __attribute__((export_name("TS_get_lib_version_string"))) TS_get_lib_version_string() {
return str_ref_to_ts("<git_version_ldk_garbagecollected>", strlen("<git_version_ldk_garbagecollected>"));
}"""
self.hu_struct_file_prefix = """
import { CommonBase, UInt5, WitnessVersion, UnqualifiedError } from './CommonBase.mjs';
import * as bindings from '../bindings.mjs'
"""
self.hu_struct_file_suffix = ""
self.util_fn_pfx = self.hu_struct_file_prefix + "\nexport class UtilMethods extends CommonBase {\n"
self.util_fn_sfx = "}"
self.c_fn_ty_pfx = ""
self.file_ext = ".py"
self.ptr_c_ty = "uint64_t"
self.ptr_native_ty = "bigint"
self.u128_native_ty = "bigint"
self.usize_c_ty = "uint32_t"
self.usize_native_ty = "number"
self.native_zero_ptr = "0n"
self.unitary_enum_c_ty = "uint32_t"
self.ptr_arr = "ptrArray"
self.is_arr_some_check = ("", " != 0")
self.get_native_arr_len_call = ("", "->arr_len")
def bindings_footer(self):
return ""
def release_native_arr_ptr_call(self, ty_info, arr_var, arr_ptr_var):
return None
def create_native_arr_call(self, arr_len, ty_info):
if ty_info.c_ty == "ptrArray":
assert ty_info.rust_obj == "LDKCVec_U5Z" or (ty_info.subty is not None and ty_info.subty.c_ty.endswith("Array"))
return "init_" + ty_info.c_ty + "(" + arr_len + ", __LINE__)"
def set_native_arr_contents(self, arr_name, arr_len, ty_info):
if ty_info.c_ty == "int8_tArray":
return ("memcpy(" + arr_name + "->elems, ", ", " + 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 copy:
return "memcpy(" + dest_name + ", " + arr_name + "->elems, " + arr_len + "); FREE(" + arr_name + ")"
assert not copy
if ty_info.c_ty == "ptrArray":
return "(void*) " + arr_name + "->elems"
else:
return arr_name + "->elems"
def get_native_arr_elem(self, arr_name, idxc, ty_info):
assert False # Only called if above is None
def get_native_arr_ptr_call(self, ty_info):
if ty_info.subty is not None:
return "(" + ty_info.subty.c_ty + "*)(((uint8_t*)", ") + 8)"
return "(" + ty_info.c_ty + "*)(((uint8_t*)", ") + 8)"
def get_native_arr_entry_call(self, ty_info, arr_name, idxc, entry_access):
return None
def cleanup_native_arr_ref_contents(self, arr_name, dest_name, arr_len, ty_info):
if ty_info.c_ty == "int8_tArray":
return "FREE(" + arr_name + ");"
else:
return "FREE(" + arr_name + ")"
def map_hu_array_elems(self, arr_name, conv_name, arr_ty, elem_ty):
if elem_ty.rust_obj == "LDKU5":
return arr_name + " != null ? bindings.uint5ArrToBytes(" + arr_name + ") : null"
assert elem_ty.c_ty == "uint64_t" or elem_ty.c_ty.endswith("Array")
return arr_name + " != null ? " + arr_name + ".map(" + conv_name + " => " + elem_ty.from_hu_conv[0] + ") : null"
def str_ref_to_native_call(self, var_name, str_len):
return "str_ref_to_ts(" + var_name + ", " + str_len + ")"
def str_ref_to_c_call(self, var_name):
return "str_ref_to_owned_c(" + var_name + ")"
def str_to_hu_conv(self, var_name):
return "const " + var_name + "_conv: string = bindings.decodeString(" + var_name + ");"
def str_from_hu_conv(self, var_name):
return ("bindings.encodeString(" + var_name + ")", "")
def c_fn_name_define_pfx(self, fn_name, have_args):
return " __attribute__((export_name(\"TS_" + fn_name + "\"))) TS_" + fn_name + "("
def init_str(self):
return ""
def get_java_arr_len(self, arr_name):
return "bindings.getArrayLength(" + arr_name + ")"
def get_java_arr_elem(self, elem_ty, arr_name, idx):
if elem_ty.c_ty.endswith("Array") or elem_ty.c_ty == "uintptr_t":
return "bindings.getU32ArrayElem(" + arr_name + ", " + idx + ")"
elif elem_ty.c_ty == "uint64_t":
return "bindings.getU64ArrayElem(" + arr_name + ", " + idx + ")"
elif elem_ty.rust_obj == "LDKU5":
return "bindings.getU8ArrayElem(" + arr_name + ", " + idx + ")"
else:
assert False
def constr_hu_array(self, ty_info, arr_len):
return "new Array(" + arr_len + ").fill(null)"
def cleanup_converted_native_array(self, ty_info, arr_name):
return "bindings.freeWasmMemory(" + arr_name + ")"
def primitive_arr_from_hu(self, arr_ty, fixed_len, arr_name):
mapped_ty = arr_ty.subty
inner = arr_name
if arr_ty.rust_obj == "LDKU128":
return ("bindings.encodeUint128(" + inner + ")", "")
if fixed_len is not None:
assert mapped_ty.c_ty == "int8_t"
inner = "bindings.check_arr_len(" + arr_name + ", " + fixed_len + ")"
if mapped_ty.c_ty.endswith("Array"):
return ("bindings.encodeUint32Array(" + inner + ")", "")
elif mapped_ty.c_ty == "uint8_t" or mapped_ty.c_ty == "int8_t":
return ("bindings.encodeUint8Array(" + inner + ")", "")
elif mapped_ty.c_ty == "uint32_t":
return ("bindings.encodeUint32Array(" + inner + ")", "")
elif mapped_ty.c_ty == "int64_t" or mapped_ty.c_ty == "uint64_t":
return ("bindings.encodeUint64Array(" + inner + ")", "")
else:
print(mapped_ty.c_ty)
assert False
def primitive_arr_to_hu(self, arr_ty, fixed_len, arr_name, conv_name):
mapped_ty = arr_ty.subty
if arr_ty.rust_obj == "LDKU128":
return "const " + conv_name + ": bigint = bindings.decodeUint128(" + arr_name + ");"
elif mapped_ty.c_ty == "uint8_t" or mapped_ty.c_ty == "int8_t":
return "const " + conv_name + ": Uint8Array = bindings.decodeUint8Array(" + arr_name + ");"
elif mapped_ty.c_ty == "uint64_t" or mapped_ty.c_ty == "int64_t":
return "const " + conv_name + ": bigint[] = bindings.decodeUint64Array(" + arr_name + ");"
else:
assert False
def var_decl_statement(self, ty_string, var_name, statement):
return "const " + var_name + ": " + ty_string + " = " + statement
def java_arr_ty_str(self, elem_ty_str):
return "number"
def for_n_in_range(self, n, minimum, maximum):
return "for (var " + n + " = " + minimum + "; " + n + " < " + maximum + "; " + n + "++) {"
def for_n_in_arr(self, n, arr_name, arr_elem_ty):
return (arr_name + ".forEach((" + n + ": " + arr_elem_ty.java_hu_ty + ") => { ", " })")
def get_ptr(self, var):
return "CommonBase.get_ptr_of(" + var + ")"
def set_null_skip_free(self, var):
return "CommonBase.set_null_skip_free(" + var + ");"
def add_ref(self, holder, referent):
return "CommonBase.add_ref_from(" + holder + ", " + referent + ")"
def obj_defined(self, struct_names, folder):
with open(self.outdir + "/index.py", 'a') as index:
index.write(f"export * from './{folder}/{struct_names[0]}.mjs';\n")
with open(self.outdir + "/imports.py.part", 'a') as imports:
imports.write(f"import {{ {', '.join(struct_names)} }} from '../{folder}/{struct_names[0]}.mjs';\n")
def fully_qualified_hu_ty_path(self, ty):
return ty.java_hu_ty
def native_c_unitary_enum_map(self, struct_name, variants, enum_doc_comment):
out_c = "static inline LDK" + struct_name + " LDK" + struct_name + "_from_js(int32_t ord) {\n"
out_c = out_c + "\tswitch (ord) {\n"
ord_v = 0
out_typescript_enum_fields = ""
for var, var_docs in variants:
out_c = out_c + "\t\tcase %d: return %s;\n" % (ord_v, var)
ord_v = ord_v + 1
if var_docs is not None:
var_docs_repld = var_docs.replace("\n", "\n\t")
out_typescript_enum_fields += f"/**\n\t * {var_docs_repld}\n\t */\n"
out_typescript_enum_fields += f"\t{var},\n\t"
out_c = out_c + "\t}\n"
out_c = out_c + "\tabort();\n"
out_c = out_c + "}\n"
out_c = out_c + "static inline int32_t LDK" + struct_name + "_to_js(LDK" + struct_name + " val) {\n"
out_c = out_c + "\tswitch (val) {\n"
ord_v = 0
for var, _ in variants:
out_c = out_c + "\t\tcase " + var + ": return %d;\n" % ord_v
ord_v = ord_v + 1
out_c = out_c + "\t\tdefault: abort();\n"
out_c = out_c + "\t}\n"
out_c = out_c + "}\n"
# Note that this is *not* marked /* @internal */ as we re-expose it directly in enums/
enum_comment_formatted = enum_doc_comment.replace("\n", "\n * ")
out_typescript = f"""
/**
* {enum_comment_formatted}
*/
export enum {struct_name} {{
{out_typescript_enum_fields}
}}
"""
out_typescript_enum = f"export {{ {struct_name} }} from \"../bindings.mjs\";"
self.obj_defined([struct_name], "enums")
return (out_c, out_typescript_enum, out_typescript)
def c_unitary_enum_to_native_call(self, ty_info):
return (ty_info.rust_obj + "_to_js(", ")")
def native_unitary_enum_to_c_call(self, ty_info):
return (ty_info.rust_obj + "_from_js(", ")")
def native_c_map_trait(self, struct_name, field_var_conversions, flattened_field_var_conversions, field_function_lines, trait_doc_comment):
out_typescript_bindings = ""
super_instantiator = ""
bindings_instantiator = ""
pointer_to_adder = ""
impl_constructor_arguments = ""
for var in flattened_field_var_conversions:
if isinstance(var, ConvInfo):
impl_constructor_arguments += f", {var.arg_name}: {var.java_hu_ty}"
super_instantiator += first_to_lower(var.arg_name) + ", "
if var.from_hu_conv is not None:
bindings_instantiator += ", " + var.from_hu_conv[0]
if var.from_hu_conv[1] != "":
pointer_to_adder += "\t\t\t" + var.from_hu_conv[1] + ";\n"
else:
bindings_instantiator += ", " + first_to_lower(var.arg_name)
else:
bindings_instantiator += ", " + first_to_lower(var[1]) + ".instance_idx!"
super_instantiator += first_to_lower(var[1]) + "_impl, "
pointer_to_adder += "\t\timpl_holder.held.ptrs_to.push(" + first_to_lower(var[1]) + ");\n"
impl_constructor_arguments += f", {first_to_lower(var[1])}_impl: {var[0].replace('LDK', '')}Interface"
super_constructor_statements = ""
trait_constructor_arguments = ""
for var in field_var_conversions:
if isinstance(var, ConvInfo):
trait_constructor_arguments += ", " + var.arg_name
else:
super_constructor_statements += "\t\tconst " + first_to_lower(var[1]) + " = " + var[1] + ".new_impl(" + super_instantiator + ");\n"
trait_constructor_arguments += ", " + first_to_lower(var[1]) + ".instance_idx!"
for suparg in var[2]:
if isinstance(suparg, ConvInfo):
trait_constructor_arguments += ", " + suparg.arg_name
else:
trait_constructor_arguments += ", " + suparg[1]
# BUILD INTERFACE METHODS
out_java_interface = ""
out_interface_implementation_overrides = ""
java_methods = []
for fn_line in field_function_lines:
java_method_descriptor = ""
if fn_line.fn_name != "free" and fn_line.fn_name != "cloned":
out_java_interface += "\t/**" + fn_line.docs.replace("\n", "\n\t * ") + "\n\t */\n"
out_java_interface += "\t" + fn_line.fn_name + "("
out_interface_implementation_overrides += f"\t\t\t{fn_line.fn_name} ("
for idx, arg_conv_info in enumerate(fn_line.args_ty):
if idx >= 1:
out_java_interface += ", "
out_interface_implementation_overrides += ", "
out_java_interface += f"{arg_conv_info.arg_name}: {arg_conv_info.java_hu_ty}"
out_interface_implementation_overrides += f"{arg_conv_info.arg_name}: {arg_conv_info.java_ty}"
java_method_descriptor += arg_conv_info.java_fn_ty_arg
out_java_interface += f"): {fn_line.ret_ty_info.java_hu_ty};\n"
java_method_descriptor += ")" + fn_line.ret_ty_info.java_fn_ty_arg
java_methods.append((fn_line.fn_name, java_method_descriptor))
out_interface_implementation_overrides += f"): {fn_line.ret_ty_info.java_ty} {{\n"
for arg_info in fn_line.args_ty:
if arg_info.to_hu_conv is not None:
out_interface_implementation_overrides += "\t\t\t\t" + arg_info.to_hu_conv.replace("\n", "\n\t\t\t\t") + "\n"
if fn_line.ret_ty_info.java_ty != "void":
out_interface_implementation_overrides += "\t\t\t\tconst ret: " + fn_line.ret_ty_info.java_hu_ty + " = arg." + fn_line.fn_name + "("
else:
out_interface_implementation_overrides += f"\t\t\t\targ." + fn_line.fn_name + "("
for idx, arg_info in enumerate(fn_line.args_ty):
if idx != 0:
out_interface_implementation_overrides += ", "
if arg_info.to_hu_conv_name is not None:
out_interface_implementation_overrides += arg_info.to_hu_conv_name
else:
out_interface_implementation_overrides += arg_info.arg_name
out_interface_implementation_overrides += ");\n"
if fn_line.ret_ty_info.java_ty != "void":
if fn_line.ret_ty_info.from_hu_conv is not None:
out_interface_implementation_overrides += "\t\t\t\t" + f"const result: {fn_line.ret_ty_info.java_ty} = " + fn_line.ret_ty_info.from_hu_conv[0].replace("\n", "\n\t\t\t\t") + ";\n"
if fn_line.ret_ty_info.from_hu_conv[1] != "":
out_interface_implementation_overrides += "\t\t\t\t" + fn_line.ret_ty_info.from_hu_conv[1].replace("this", "impl_holder.held").replace("\n", "\n\t\t\t\t") + ";\n"
#if fn_line.ret_ty_info.rust_obj in result_types:
# XXX: We need to handle this in conversion logic so that its cross-language!
# Avoid double-free by breaking the result - we should learn to clone these and then we can be safe instead
# out_interface_implementation_overrides = out_interface_implementation_overrides + "\t\t\t\tret.ptr = 0;\n"
out_interface_implementation_overrides += "\t\t\t\treturn result;\n"
else:
out_interface_implementation_overrides += "\t\t\t\treturn ret;\n"
out_interface_implementation_overrides += f"\t\t\t}},\n"
formatted_trait_docs = trait_doc_comment.replace("\n", "\n * ")
out_typescript_human = f"""
{self.hu_struct_file_prefix}
/** An implementation of {struct_name.replace("LDK","")} */
export interface {struct_name.replace("LDK", "")}Interface {{
{out_java_interface}}}
class {struct_name}Holder {{
held: {struct_name.replace("LDK", "")}|null = null;
}}
/**
* {formatted_trait_docs}
*/
export class {struct_name.replace("LDK","")} extends CommonBase {{
/* @internal */
public bindings_instance: bindings.{struct_name}|null;
/* @internal */
public instance_idx?: number;
/* @internal */
constructor(_dummy: null, ptr: bigint) {{
super(ptr, bindings.{struct_name.replace("LDK","")}_free);
this.bindings_instance = null;
}}
/** Creates a new instance of {struct_name.replace("LDK","")} from a given implementation */
public static new_impl(arg: {struct_name.replace("LDK", "")}Interface{impl_constructor_arguments}): {struct_name.replace("LDK", "")} {{
const impl_holder: {struct_name}Holder = new {struct_name}Holder();
let structImplementation = {{
{out_interface_implementation_overrides} }} as bindings.{struct_name};
{super_constructor_statements} const ptr_idx: [bigint, number] = bindings.{struct_name}_new(structImplementation{bindings_instantiator});
impl_holder.held = new {struct_name.replace("LDK", "")}(null, ptr_idx[0]);
impl_holder.held.instance_idx = ptr_idx[1];
impl_holder.held.bindings_instance = structImplementation;
{pointer_to_adder} return impl_holder.held!;
}}
"""