mirror of
https://github.com/denoland/deno.git
synced 2024-12-01 16:51:13 -05:00
feat(ext/ffi): Safe number pointers (#15173)
This commit is contained in:
parent
f6f0215d87
commit
3893ce848c
8 changed files with 713 additions and 443 deletions
27
cli/dts/lib.deno.unstable.d.ts
vendored
27
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -366,9 +366,9 @@ declare namespace Deno {
|
|||
|
||||
type ToNativeTypeMap =
|
||||
& Record<NativeNumberType, number>
|
||||
& Record<NativeBigIntType, bigint | number>
|
||||
& Record<NativePointerType, TypedArray | bigint | null>
|
||||
& Record<NativeFunctionType, bigint | null>;
|
||||
& Record<NativeBigIntType, PointerValue>
|
||||
& Record<NativePointerType, TypedArray | PointerValue | null>
|
||||
& Record<NativeFunctionType, PointerValue | null>;
|
||||
|
||||
/** Type conversion for foreign symbol parameters and unsafe callback return types */
|
||||
type ToNativeType<T extends NativeType = NativeType> = ToNativeTypeMap[T];
|
||||
|
@ -391,9 +391,9 @@ declare namespace Deno {
|
|||
|
||||
type FromNativeTypeMap =
|
||||
& Record<NativeNumberType, number>
|
||||
& Record<NativeBigIntType, bigint>
|
||||
& Record<NativePointerType, bigint>
|
||||
& Record<NativeFunctionType, bigint>;
|
||||
& Record<NativeBigIntType, PointerValue>
|
||||
& Record<NativePointerType, PointerValue>
|
||||
& Record<NativeFunctionType, PointerValue>;
|
||||
|
||||
/** Type conversion for foreign symbol return types and unsafe callback parameters */
|
||||
type FromNativeType<T extends NativeType = NativeType> = FromNativeTypeMap[T];
|
||||
|
@ -481,6 +481,15 @@ declare namespace Deno {
|
|||
| BigInt64Array
|
||||
| BigUint64Array;
|
||||
|
||||
/**
|
||||
* Pointer type depends on the architecture and actual pointer value.
|
||||
*
|
||||
* On a 32 bit system all pointer values are plain numbers. On a 64 bit
|
||||
* system pointer values are represented as numbers if the value is below
|
||||
* `Number.MAX_SAFE_INTEGER`.
|
||||
*/
|
||||
export type PointerValue = number | bigint;
|
||||
|
||||
/** **UNSTABLE**: Unsafe and new API, beware!
|
||||
*
|
||||
* An unsafe pointer to a memory location for passing and returning pointers to and from the ffi
|
||||
|
@ -489,7 +498,7 @@ declare namespace Deno {
|
|||
/**
|
||||
* Return the direct memory pointer to the typed array in memory
|
||||
*/
|
||||
static of(value: Deno.UnsafeCallback | TypedArray): bigint;
|
||||
static of(value: Deno.UnsafeCallback | TypedArray): PointerValue;
|
||||
}
|
||||
|
||||
/** **UNSTABLE**: Unsafe and new API, beware!
|
||||
|
@ -517,9 +526,9 @@ declare namespace Deno {
|
|||
/** Gets a signed 32-bit integer at the specified byte offset from the pointer. */
|
||||
getInt32(offset?: number): number;
|
||||
/** Gets an unsigned 64-bit integer at the specified byte offset from the pointer. */
|
||||
getBigUint64(offset?: number): bigint;
|
||||
getBigUint64(offset?: number): PointerValue;
|
||||
/** Gets a signed 64-bit integer at the specified byte offset from the pointer. */
|
||||
getBigInt64(offset?: number): bigint;
|
||||
getBigInt64(offset?: number): PointerValue;
|
||||
/** Gets a signed 32-bit float at the specified byte offset from the pointer. */
|
||||
getFloat32(offset?: number): number;
|
||||
/** Gets a signed 64-bit float at the specified byte offset from the pointer. */
|
||||
|
|
|
@ -12,11 +12,19 @@
|
|||
TypeError,
|
||||
} = window.__bootstrap.primordials;
|
||||
|
||||
function unpackU64([hi, lo]) {
|
||||
function unpackU64(returnValue) {
|
||||
if (typeof returnValue === "number") {
|
||||
return returnValue;
|
||||
}
|
||||
const [hi, lo] = returnValue;
|
||||
return BigInt(hi) << 32n | BigInt(lo);
|
||||
}
|
||||
|
||||
function unpackI64([hi, lo]) {
|
||||
function unpackI64(returnValue) {
|
||||
if (typeof returnValue === "number") {
|
||||
return returnValue;
|
||||
}
|
||||
const [hi, lo] = returnValue;
|
||||
const u64 = unpackU64([hi, lo]);
|
||||
return u64 >> 63n ? u64 - 0x10000000000000000n : u64;
|
||||
}
|
||||
|
@ -31,77 +39,77 @@
|
|||
getUint8(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_u8",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getInt8(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_i8",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getUint16(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_u16",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getInt16(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_i16",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getUint32(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_u32",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getInt32(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_i32",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getBigUint64(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_u64",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getBigInt64(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_u64",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
"op_ffi_read_i64",
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getFloat32(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_f32",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getFloat64(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_read_f64",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
getCString(offset = 0) {
|
||||
return core.opSync(
|
||||
"op_ffi_cstr_read",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -116,7 +124,7 @@
|
|||
copyInto(destination, offset = 0) {
|
||||
core.opSync(
|
||||
"op_ffi_buf_copy_into",
|
||||
offset ? this.pointer + BigInt(offset) : this.pointer,
|
||||
offset ? BigInt(this.pointer) + BigInt(offset) : this.pointer,
|
||||
destination,
|
||||
destination.byteLength,
|
||||
);
|
||||
|
|
|
@ -5,6 +5,9 @@ use crate::{tcc::Compiler, Symbol};
|
|||
use std::ffi::c_void;
|
||||
use std::ffi::CString;
|
||||
use std::fmt::Write as _;
|
||||
use std::mem::size_of;
|
||||
|
||||
const _: () = assert!(size_of::<fn()>() == size_of::<usize>());
|
||||
|
||||
pub(crate) struct Allocation {
|
||||
pub addr: *mut c_void,
|
||||
|
@ -22,12 +25,14 @@ fn native_arg_to_c(ty: &NativeType) -> &'static str {
|
|||
match ty {
|
||||
NativeType::U8 | NativeType::U16 | NativeType::U32 => "uint32_t",
|
||||
NativeType::I8 | NativeType::I16 | NativeType::I32 => "int32_t",
|
||||
NativeType::U64 | NativeType::USize => "uint64_t",
|
||||
NativeType::I64 | NativeType::ISize => "int64_t",
|
||||
NativeType::Void => "void",
|
||||
NativeType::F32 => "float",
|
||||
NativeType::F64 => "double",
|
||||
_ => unimplemented!(),
|
||||
NativeType::U64 => "uint64_t",
|
||||
NativeType::I64 => "int64_t",
|
||||
NativeType::ISize => "intptr_t",
|
||||
NativeType::USize => "uintptr_t",
|
||||
NativeType::Pointer | NativeType::Function => "void*",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,9 +47,11 @@ fn native_to_c(ty: &NativeType) -> &'static str {
|
|||
NativeType::Void => "void",
|
||||
NativeType::F32 => "float",
|
||||
NativeType::F64 => "double",
|
||||
NativeType::U64 | NativeType::USize => "uint64_t",
|
||||
NativeType::I64 | NativeType::ISize => "int64_t",
|
||||
_ => unimplemented!(),
|
||||
NativeType::U64 => "uint64_t",
|
||||
NativeType::I64 => "int64_t",
|
||||
NativeType::ISize => "intptr_t",
|
||||
NativeType::USize => "uintptr_t",
|
||||
NativeType::Pointer | NativeType::Function => "void*",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,16 +187,16 @@ mod tests {
|
|||
assert_eq!(
|
||||
codegen(vec![NativeType::ISize, NativeType::U64], NativeType::Void),
|
||||
"#include <stdint.h>\n\n\
|
||||
extern void func(int64_t p0, uint64_t p1);\n\n\
|
||||
void func_trampoline(void* recv, int64_t p0, uint64_t p1) {\
|
||||
extern void func(intptr_t p0, uint64_t p1);\n\n\
|
||||
void func_trampoline(void* recv, intptr_t p0, uint64_t p1) {\
|
||||
\n return func(p0, p1);\n\
|
||||
}\n\n"
|
||||
);
|
||||
assert_eq!(
|
||||
codegen(vec![NativeType::USize, NativeType::USize], NativeType::U32),
|
||||
"#include <stdint.h>\n\n\
|
||||
extern uint32_t func(uint64_t p0, uint64_t p1);\n\n\
|
||||
uint32_t func_trampoline(void* recv, uint64_t p0, uint64_t p1) {\
|
||||
extern uint32_t func(uintptr_t p0, uintptr_t p1);\n\n\
|
||||
uint32_t func_trampoline(void* recv, uintptr_t p0, uintptr_t p1) {\
|
||||
\n return func(p0, p1);\n\
|
||||
}\n\n"
|
||||
);
|
||||
|
|
948
ext/ffi/lib.rs
948
ext/ffi/lib.rs
File diff suppressed because it is too large
Load diff
|
@ -305,6 +305,11 @@ Deno.bench("nop_buffer()", () => {
|
|||
nop_buffer(buffer);
|
||||
});
|
||||
|
||||
const buffer_ptr = Deno.UnsafePointer.of(buffer);
|
||||
Deno.bench("nop_buffer() number", () => {
|
||||
nop_buffer(buffer_ptr);
|
||||
});
|
||||
|
||||
const { return_u8 } = dylib.symbols;
|
||||
Deno.bench("return_u8()", () => {
|
||||
return_u8();
|
||||
|
@ -442,6 +447,10 @@ Deno.bench("nop_buffer_nonblocking()", async () => {
|
|||
await nop_buffer_nonblocking(buffer);
|
||||
});
|
||||
|
||||
Deno.bench("nop_buffer_nonblocking() number", async () => {
|
||||
await nop_buffer_nonblocking(buffer_ptr);
|
||||
});
|
||||
|
||||
const { return_u8_nonblocking } = dylib.symbols;
|
||||
Deno.bench("return_u8_nonblocking()", async () => {
|
||||
await return_u8_nonblocking();
|
||||
|
@ -540,6 +549,38 @@ Deno.bench("nop_many_parameters()", () => {
|
|||
);
|
||||
});
|
||||
|
||||
const buffer2_ptr = Deno.UnsafePointer.of(buffer2);
|
||||
Deno.bench("nop_many_parameters() number", () => {
|
||||
nop_many_parameters(
|
||||
135,
|
||||
47,
|
||||
356,
|
||||
-236,
|
||||
7457,
|
||||
-1356,
|
||||
16471468,
|
||||
-1334748136,
|
||||
132658769535,
|
||||
-42745856824,
|
||||
13567.26437,
|
||||
7.686234e-3,
|
||||
buffer_ptr,
|
||||
64,
|
||||
-42,
|
||||
83,
|
||||
-136,
|
||||
3657,
|
||||
-2376,
|
||||
3277918,
|
||||
-474628146,
|
||||
344657895,
|
||||
-2436732,
|
||||
135.26437e3,
|
||||
264.3576468623546834,
|
||||
buffer2_ptr,
|
||||
);
|
||||
});
|
||||
|
||||
const { nop_many_parameters_nonblocking } = dylib.symbols;
|
||||
Deno.bench("nop_many_parameters_nonblocking()", () => {
|
||||
nop_many_parameters_nonblocking(
|
||||
|
|
|
@ -131,7 +131,7 @@ remote.symbols.method14(null);
|
|||
remote.symbols.method14(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method15(0);
|
||||
remote.symbols.method15("foo");
|
||||
remote.symbols.method15(new Uint16Array(1));
|
||||
remote.symbols.method15(0n);
|
||||
|
||||
|
@ -245,16 +245,16 @@ remote.symbols.method20(unsafe_callback_right1.pointer);
|
|||
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static1_wrong: null = remote.symbols.static1;
|
||||
const static1_right: bigint = remote.symbols.static1;
|
||||
const static1_right: Deno.PointerValue = remote.symbols.static1;
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static2_wrong: null = remote.symbols.static2;
|
||||
const static2_right: Deno.UnsafePointer = remote.symbols.static2;
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static3_wrong: null = remote.symbols.static3;
|
||||
const static3_right: bigint = remote.symbols.static3;
|
||||
const static3_right: Deno.PointerValue = remote.symbols.static3;
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static4_wrong: null = remote.symbols.static4;
|
||||
const static4_right: bigint = remote.symbols.static4;
|
||||
const static4_right: Deno.PointerValue = remote.symbols.static4;
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static5_wrong: null = remote.symbols.static5;
|
||||
const static5_right: number = remote.symbols.static5;
|
||||
|
@ -266,7 +266,7 @@ const static7_wrong: null = remote.symbols.static7;
|
|||
const static7_right: number = remote.symbols.static7;
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static8_wrong: null = remote.symbols.static8;
|
||||
const static8_right: bigint = remote.symbols.static8;
|
||||
const static8_right: Deno.PointerValue = remote.symbols.static8;
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static9_wrong: null = remote.symbols.static9;
|
||||
const static9_right: number = remote.symbols.static9;
|
||||
|
@ -278,7 +278,7 @@ const static11_wrong: null = remote.symbols.static11;
|
|||
const static11_right: number = remote.symbols.static11;
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static12_wrong: null = remote.symbols.static12;
|
||||
const static12_right: bigint = remote.symbols.static12;
|
||||
const static12_right: Deno.PointerValue = remote.symbols.static12;
|
||||
// @ts-expect-error: Invalid member type
|
||||
const static13_wrong: null = remote.symbols.static13;
|
||||
const static13_right: number = remote.symbols.static13;
|
||||
|
@ -331,7 +331,10 @@ type __Tests__ = [
|
|||
higher_order_params: AssertEqual<
|
||||
{
|
||||
symbols: {
|
||||
pushBuf: (ptr: bigint | TypedArray | null, func: bigint | null) => void;
|
||||
pushBuf: (
|
||||
ptr: number | bigint | TypedArray | null,
|
||||
func: number | bigint | null,
|
||||
) => void;
|
||||
};
|
||||
close(): void;
|
||||
},
|
||||
|
@ -343,9 +346,9 @@ type __Tests__ = [
|
|||
{
|
||||
symbols: {
|
||||
pushBuf: (
|
||||
ptr: bigint | TypedArray | null,
|
||||
func: bigint | null,
|
||||
) => bigint;
|
||||
ptr: number | bigint | TypedArray | null,
|
||||
func: number | bigint | null,
|
||||
) => number | bigint;
|
||||
};
|
||||
close(): void;
|
||||
},
|
||||
|
@ -356,7 +359,9 @@ type __Tests__ = [
|
|||
non_exact_params: AssertEqual<
|
||||
{
|
||||
symbols: {
|
||||
foo: (...args: (number | bigint | TypedArray | null)[]) => bigint;
|
||||
foo: (
|
||||
...args: (number | bigint | TypedArray | null)[]
|
||||
) => number | bigint;
|
||||
};
|
||||
close(): void;
|
||||
},
|
||||
|
|
|
@ -66,17 +66,29 @@ fn basic() {
|
|||
5\n\
|
||||
5\n\
|
||||
579\n\
|
||||
8589934590n\n\
|
||||
-8589934590n\n\
|
||||
8589934590n\n\
|
||||
-8589934590n\n\
|
||||
8589934590\n\
|
||||
-8589934590\n\
|
||||
8589934590\n\
|
||||
-8589934590\n\
|
||||
9007199254740992n\n\
|
||||
9007199254740992n\n\
|
||||
-9007199254740992n\n\
|
||||
9007199254740992n\n\
|
||||
9007199254740992n\n\
|
||||
-9007199254740992n\n\
|
||||
579.9119873046875\n\
|
||||
579.912\n\
|
||||
579\n\
|
||||
8589934590n\n\
|
||||
-8589934590n\n\
|
||||
8589934590n\n\
|
||||
-8589934590n\n\
|
||||
8589934590\n\
|
||||
-8589934590\n\
|
||||
8589934590\n\
|
||||
-8589934590\n\
|
||||
9007199254740992n\n\
|
||||
9007199254740992n\n\
|
||||
-9007199254740992n\n\
|
||||
9007199254740992n\n\
|
||||
9007199254740992n\n\
|
||||
-9007199254740992n\n\
|
||||
579.9119873046875\n\
|
||||
579.912\n\
|
||||
After sleep_blocking\n\
|
||||
|
@ -86,7 +98,7 @@ fn basic() {
|
|||
After\n\
|
||||
true\n\
|
||||
logCallback\n\
|
||||
1 -1 2 -2 3 -3 4n -4n 0.5 -0.5 1 2 3 4 5 6 7 8\n\
|
||||
1 -1 2 -2 3 -3 4 -4 0.5 -0.5 1 2 3 4 5 6 7 8\n\
|
||||
u8: 8\n\
|
||||
buf: [1, 2, 3, 4, 5, 6, 7, 8]\n\
|
||||
logCallback\n\
|
||||
|
@ -98,7 +110,7 @@ fn basic() {
|
|||
Thread safe call counter: 1\n\
|
||||
u8: 8\n\
|
||||
Static u32: 42\n\
|
||||
Static i64: -1242464576485n\n\
|
||||
Static i64: -1242464576485\n\
|
||||
Static ptr: true\n\
|
||||
Static ptr value: 42\n\
|
||||
arrayBuffer.byteLength: 4\n\
|
||||
|
@ -116,7 +128,7 @@ fn symbol_types() {
|
|||
build();
|
||||
|
||||
let output = deno_cmd()
|
||||
.arg("cache")
|
||||
.arg("check")
|
||||
.arg("--unstable")
|
||||
.arg("--quiet")
|
||||
.arg("tests/ffi_types.ts")
|
||||
|
|
|
@ -265,6 +265,12 @@ console.log(dylib.symbols.add_u64(0xffffffffn, 0xffffffffn));
|
|||
console.log(dylib.symbols.add_i64(-0xffffffffn, -0xffffffffn));
|
||||
console.log(dylib.symbols.add_usize(0xffffffffn, 0xffffffffn));
|
||||
console.log(dylib.symbols.add_isize(-0xffffffffn, -0xffffffffn));
|
||||
console.log(dylib.symbols.add_u64(Number.MAX_SAFE_INTEGER, 1));
|
||||
console.log(dylib.symbols.add_i64(Number.MAX_SAFE_INTEGER, 1));
|
||||
console.log(dylib.symbols.add_i64(Number.MIN_SAFE_INTEGER, -1));
|
||||
console.log(dylib.symbols.add_usize(Number.MAX_SAFE_INTEGER, 1));
|
||||
console.log(dylib.symbols.add_isize(Number.MAX_SAFE_INTEGER, 1));
|
||||
console.log(dylib.symbols.add_isize(Number.MIN_SAFE_INTEGER, -1));
|
||||
console.log(dylib.symbols.add_f32(123.123, 456.789));
|
||||
console.log(dylib.symbols.add_f64(123.123, 456.789));
|
||||
|
||||
|
@ -280,6 +286,12 @@ console.log(
|
|||
console.log(
|
||||
await dylib.symbols.add_isize_nonblocking(-0xffffffffn, -0xffffffffn),
|
||||
);
|
||||
console.log(await dylib.symbols.add_u64_nonblocking(Number.MAX_SAFE_INTEGER, 1));
|
||||
console.log(await dylib.symbols.add_i64_nonblocking(Number.MAX_SAFE_INTEGER, 1));
|
||||
console.log(await dylib.symbols.add_i64_nonblocking(Number.MIN_SAFE_INTEGER, -1));
|
||||
console.log(await dylib.symbols.add_usize_nonblocking(Number.MAX_SAFE_INTEGER, 1));
|
||||
console.log(await dylib.symbols.add_isize_nonblocking(Number.MAX_SAFE_INTEGER, 1));
|
||||
console.log(await dylib.symbols.add_isize_nonblocking(Number.MIN_SAFE_INTEGER, -1));
|
||||
console.log(await dylib.symbols.add_f32_nonblocking(123.123, 456.789));
|
||||
console.log(await dylib.symbols.add_f64_nonblocking(123.123, 456.789));
|
||||
|
||||
|
@ -439,7 +451,7 @@ console.log("Static u32:", dylib.symbols.static_u32);
|
|||
console.log("Static i64:", dylib.symbols.static_i64);
|
||||
console.log(
|
||||
"Static ptr:",
|
||||
typeof dylib.symbols.static_ptr === "bigint",
|
||||
typeof dylib.symbols.static_ptr === "number",
|
||||
);
|
||||
const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr);
|
||||
console.log("Static ptr value:", view.getUint32());
|
||||
|
|
Loading…
Reference in a new issue