Skip to content

Commit 7ad291d

Browse files
hyperpolymathclaude
andcommitted
fix: add SAFETY comments to all unsafe pointer casts in FFI modules
Documents 62 unsafe pointer operations (@ptrCast, @aligncast, @constcast) with // SAFETY: comments explaining why each cast is safe, per panic-attack security audit findings (13 High severity). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a0610e1 commit 7ad291d

10 files changed

Lines changed: 62 additions & 0 deletions

File tree

ffi/zig/src/cache.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,14 @@ export fn ddac_cache_init(
111111
.allocator = allocator,
112112
};
113113

114+
// SAFETY: state was just allocated by c_allocator.create(CacheState), which returns a well-aligned *CacheState
114115
return @ptrCast(state);
115116
}
116117

117118
/// Free the LMDB cache. Safe to call with null.
118119
export fn ddac_cache_free(handle: ?*anyopaque) void {
119120
const ptr = handle orelse return;
121+
// SAFETY: ptr originates from ddac_cache_init() which stores a *CacheState via @ptrCast; alignment is guaranteed by c_allocator
120122
const state: *CacheState = @ptrCast(@alignCast(ptr));
121123

122124
lmdb.mdb_env_close(state.env);
@@ -139,6 +141,7 @@ export fn ddac_cache_lookup(
139141
result_size: usize,
140142
) c_int {
141143
const ptr = handle orelse return 0;
144+
// SAFETY: ptr originates from ddac_cache_init() which stores a *CacheState via @ptrCast; alignment is guaranteed by c_allocator
142145
const state: *CacheState = @ptrCast(@alignCast(ptr));
143146
const path = doc_path orelse return 0;
144147
const out = result_out orelse return 0;
@@ -150,6 +153,8 @@ export fn ddac_cache_lookup(
150153

151154
// Look up by path
152155
const path_slice = std.mem.span(path);
156+
// SAFETY: @constCast is required by LMDB's C API which takes void* for keys; the data is only read, never written by mdb_get
157+
// SAFETY: @ptrCast converts [*]const u8 to *anyopaque as required by MDB_val.mv_data field
153158
var key = lmdb.MDB_val{ .mv_size = path_slice.len, .mv_data = @constCast(@ptrCast(path_slice.ptr)) };
154159
var data: lmdb.MDB_val = undefined;
155160

@@ -160,6 +165,7 @@ export fn ddac_cache_lookup(
160165
if (data.mv_size < expected_size) return 0;
161166

162167
// Check mtime and file_size
168+
// SAFETY: LMDB mdb_get returns mv_data pointing into the memory-mapped database; valid for the transaction lifetime
163169
const value_bytes: [*]const u8 = @ptrCast(data.mv_data);
164170
const cached_mtime = std.mem.readInt(i64, value_bytes[0..8], .little);
165171
const cached_size = std.mem.readInt(i64, value_bytes[8..16], .little);
@@ -187,6 +193,7 @@ export fn ddac_cache_store(
187193
result_size: usize,
188194
) void {
189195
const ptr = handle orelse return;
196+
// SAFETY: ptr originates from ddac_cache_init() which stores a *CacheState via @ptrCast; alignment is guaranteed by c_allocator
190197
const state: *CacheState = @ptrCast(@alignCast(ptr));
191198
const path = doc_path orelse return;
192199
const result_bytes = result orelse return;
@@ -205,7 +212,10 @@ export fn ddac_cache_store(
205212
if (lmdb.mdb_txn_begin(state.env, null, 0, &txn) != 0) return;
206213

207214
const path_slice = std.mem.span(path);
215+
// SAFETY: @constCast is required by LMDB's C API which takes void* for keys; the data is only read, never written by mdb_put for keys
216+
// SAFETY: @ptrCast converts [*]const u8 to *anyopaque as required by MDB_val.mv_data field
208217
var key = lmdb.MDB_val{ .mv_size = path_slice.len, .mv_data = @constCast(@ptrCast(path_slice.ptr)) };
218+
// SAFETY: value_buf is a stack-allocated array; @ptrCast converts *[N]u8 to *anyopaque for MDB_val.mv_data; valid for the transaction scope
209219
var data = lmdb.MDB_val{ .mv_size = value_size, .mv_data = @ptrCast(&value_buf) };
210220

211221
if (lmdb.mdb_put(txn, state.dbi, &key, &data, 0) != 0) {
@@ -219,6 +229,7 @@ export fn ddac_cache_store(
219229
/// Return the number of entries in the cache.
220230
export fn ddac_cache_count(handle: ?*anyopaque) u64 {
221231
const ptr = handle orelse return 0;
232+
// SAFETY: ptr originates from ddac_cache_init() which stores a *CacheState via @ptrCast; alignment is guaranteed by c_allocator
222233
const state: *CacheState = @ptrCast(@alignCast(ptr));
223234

224235
var txn: ?*lmdb.MDB_txn = null;
@@ -235,6 +246,7 @@ export fn ddac_cache_count(handle: ?*anyopaque) u64 {
235246
/// Call this periodically for durability or before graceful shutdown.
236247
export fn ddac_cache_sync(handle: ?*anyopaque) void {
237248
const ptr = handle orelse return;
249+
// SAFETY: ptr originates from ddac_cache_init() which stores a *CacheState via @ptrCast; alignment is guaranteed by c_allocator
238250
const state: *CacheState = @ptrCast(@alignCast(ptr));
239251
_ = lmdb.mdb_env_sync(state.env, 1);
240252
}

ffi/zig/src/capnp.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,13 @@ pub const Builder = struct {
312312

313313
fn wr64(self: *Builder, off: usize, val: u64) void {
314314
if (off + 8 > self.cap) return;
315+
// SAFETY: bounds check on line above ensures off + 8 <= cap; buf is caller-provided aligned buffer; align(1) permits unaligned write
315316
@as(*align(1) u64, @ptrCast(self.buf + off)).* = std.mem.nativeToLittle(u64, val);
316317
}
317318

318319
fn wr32(self: *Builder, off: usize, val: u32) void {
319320
if (off + 4 > self.cap) return;
321+
// SAFETY: bounds check on line above ensures off + 4 <= cap; buf is caller-provided aligned buffer; align(1) permits unaligned write
320322
@as(*align(1) u32, @ptrCast(self.buf + off)).* = std.mem.nativeToLittle(u32, val);
321323
}
322324

ffi/zig/src/docudactyl_ffi.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ fn parseImage(input_path: [*:0]const u8, output_path: [*:0]const u8, state: *Han
396396
result.status = 2;
397397
return;
398398
}
399+
// SAFETY: @constCast required because pixDestroy takes *?*PIX (mutable pointer) but only reads then nullifies; pix will not be used after this
399400
defer c.pixDestroy(@constCast(&pix));
400401

401402
// Check minimum dimensions — tiny images (icons, spacers) produce
@@ -809,12 +810,14 @@ export fn ddac_init() ?*anyopaque {
809810
state.vips_initialised = true;
810811
}
811812

813+
// SAFETY: state was just allocated by c_allocator.create(HandleState), which returns a well-aligned *HandleState
812814
return @ptrCast(state);
813815
}
814816

815817
/// Free all library contexts. Safe to call with null.
816818
export fn ddac_free(handle: ?*anyopaque) void {
817819
const ptr = handle orelse return;
820+
// SAFETY: ptr originates from ddac_init() which stores a *HandleState via @ptrCast; alignment is guaranteed by c_allocator
818821
const state: *HandleState = @ptrCast(@alignCast(ptr));
819822

820823
if (state.tess_api) |tess| {
@@ -853,6 +856,7 @@ export fn ddac_parse(
853856
copyToFixed(256, &result.error_msg, "Null handle");
854857
return result;
855858
};
859+
// SAFETY: ptr originates from ddac_init() which stores a *HandleState via @ptrCast; alignment is guaranteed by c_allocator
856860
const state: *HandleState = @ptrCast(@alignCast(ptr));
857861

858862
const in_path = input_path orelse {
@@ -874,6 +878,7 @@ export fn ddac_parse(
874878
result.status = 2; // FileNotFound
875879
copyToFixed(256, &result.error_msg, "File not found: ");
876880
// Append as much of the path as fits
881+
// SAFETY: error_msg is a [256]u8 array that was null-terminated by copyToFixed; casting to [*:0]const u8 is valid for std.mem.len
877882
const prefix_len = std.mem.len(@as([*:0]const u8, @ptrCast(&result.error_msg)));
878883
const remaining = 255 - prefix_len;
879884
const path_len = @min(in_slice.len, remaining);
@@ -921,6 +926,7 @@ export fn ddac_parse(
921926
.char_count = result.char_count,
922927
.duration_sec = result.duration_sec,
923928
.ocr_confidence = captured.ocr_confidence,
929+
// SAFETY: TessBaseAPI* from Tesseract C API is cast to *anyopaque for the generic StageContext; the stages module casts it back
924930
.tess_api = if (state.tess_api) |t| @ptrCast(t) else null,
925931
.ml_handle = state.ml_handle,
926932
};
@@ -935,6 +941,7 @@ export fn ddac_parse(
935941
/// (Chapel) — it will NOT be freed by ddac_free().
936942
export fn ddac_set_ml_handle(handle: ?*anyopaque, ml_handle: ?*anyopaque) void {
937943
const ptr = handle orelse return;
944+
// SAFETY: ptr originates from ddac_init() which stores a *HandleState via @ptrCast; alignment is guaranteed by c_allocator
938945
const state: *HandleState = @ptrCast(@alignCast(ptr));
939946
state.ml_handle = ml_handle;
940947
}
@@ -944,6 +951,7 @@ export fn ddac_set_ml_handle(handle: ?*anyopaque, ml_handle: ?*anyopaque) void {
944951
/// caller (Chapel) — it will NOT be freed by ddac_free().
945952
export fn ddac_set_gpu_ocr_handle(handle: ?*anyopaque, ocr_handle: ?*anyopaque) void {
946953
const ptr = handle orelse return;
954+
// SAFETY: ptr originates from ddac_init() which stores a *HandleState via @ptrCast; alignment is guaranteed by c_allocator
947955
const state: *HandleState = @ptrCast(@alignCast(ptr));
948956
state.gpu_ocr_handle = ocr_handle;
949957
}

ffi/zig/src/dragonfly.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,14 @@ export fn ddac_dragonfly_connect(host_port: [*:0]const u8) ?*anyopaque {
209209
return null;
210210
}
211211

212+
// SAFETY: handle was just allocated by c_allocator.create(DfHandle), which returns a well-aligned *DfHandle
212213
return @ptrCast(handle);
213214
}
214215

215216
/// Close and free the Dragonfly connection.
216217
export fn ddac_dragonfly_close(handle: ?*anyopaque) void {
217218
if (handle) |h| {
219+
// SAFETY: h originates from ddac_dragonfly_connect() which stores a *DfHandle via @ptrCast; alignment is guaranteed by c_allocator
218220
const df: *DfHandle = @ptrCast(@alignCast(h));
219221
df.client.close();
220222
std.heap.c_allocator.destroy(df);
@@ -231,6 +233,7 @@ export fn ddac_dragonfly_lookup(
231233
result_out: [*]u8,
232234
result_size: usize,
233235
) c_int {
236+
// SAFETY: handle originates from ddac_dragonfly_connect() which stores a *DfHandle via @ptrCast; alignment is guaranteed by c_allocator
234237
const df: *DfHandle = @ptrCast(@alignCast(handle));
235238
const sha = std.mem.span(sha256);
236239

@@ -260,6 +263,7 @@ export fn ddac_dragonfly_store(
260263
result_size: usize,
261264
ttl_secs: u32,
262265
) void {
266+
// SAFETY: handle originates from ddac_dragonfly_connect() which stores a *DfHandle via @ptrCast; alignment is guaranteed by c_allocator
263267
const df: *DfHandle = @ptrCast(@alignCast(handle));
264268
const sha = std.mem.span(sha256);
265269

@@ -276,6 +280,7 @@ export fn ddac_dragonfly_store(
276280
/// Get the number of ddac keys in the cache (approximate).
277281
/// Uses DBSIZE command.
278282
export fn ddac_dragonfly_count(handle: *anyopaque) u64 {
283+
// SAFETY: handle originates from ddac_dragonfly_connect() which stores a *DfHandle via @ptrCast; alignment is guaranteed by c_allocator
279284
const df: *DfHandle = @ptrCast(@alignCast(handle));
280285
df.client.sendCommand(&[_][]const u8{"DBSIZE"}) catch return 0;
281286
const n = df.client.readIntegerReply() catch return 0;

ffi/zig/src/gpu_ocr.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,12 +329,14 @@ fn processBatchGpu(state: *GpuOcrState) void {
329329
export fn ddac_gpu_ocr_init() ?*anyopaque {
330330
const allocator = std.heap.c_allocator;
331331
const state = GpuOcrState.init(allocator) catch return null;
332+
// SAFETY: state was just allocated by c_allocator.create(GpuOcrState), which returns a well-aligned *GpuOcrState
332333
return @ptrCast(state);
333334
}
334335

335336
/// Free the GPU OCR coprocessor.
336337
export fn ddac_gpu_ocr_free(handle: ?*anyopaque) void {
337338
const ptr = handle orelse return;
339+
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
338340
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
339341
state.deinit();
340342
}
@@ -343,6 +345,7 @@ export fn ddac_gpu_ocr_free(handle: ?*anyopaque) void {
343345
/// Returns: 0=paddle_gpu, 1=tesseract_cuda, 2=cpu_only
344346
export fn ddac_gpu_ocr_backend(handle: ?*anyopaque) u8 {
345347
const ptr = handle orelse return 2;
348+
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
346349
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
347350
return @intFromEnum(state.backend);
348351
}
@@ -356,6 +359,7 @@ export fn ddac_gpu_ocr_submit(
356359
output_path: [*:0]const u8,
357360
) c_int {
358361
const ptr = handle orelse return -1;
362+
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
359363
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
360364

361365
if (state.queue_len >= MAX_BATCH_SIZE) return -1;
@@ -381,6 +385,7 @@ export fn ddac_gpu_ocr_submit(
381385
/// Call this before collecting results if the queue isn't full.
382386
export fn ddac_gpu_ocr_flush(handle: ?*anyopaque) void {
383387
const ptr = handle orelse return;
388+
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
384389
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
385390

386391
if (state.queue_len > 0) {
@@ -391,6 +396,7 @@ export fn ddac_gpu_ocr_flush(handle: ?*anyopaque) void {
391396
/// Get the number of results ready to collect after flush.
392397
export fn ddac_gpu_ocr_results_ready(handle: ?*anyopaque) u32 {
393398
const ptr = handle orelse return 0;
399+
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
394400
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
395401
return state.results_ready;
396402
}
@@ -404,6 +410,7 @@ export fn ddac_gpu_ocr_collect(
404410
result_out: *OcrResult,
405411
) c_int {
406412
const ptr = handle orelse return -1;
413+
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
407414
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
408415

409416
if (slot_id >= state.results_ready) return -1;
@@ -427,6 +434,7 @@ export fn ddac_gpu_ocr_stats(
427434
gpu_time_us.* = 0;
428435
return;
429436
};
437+
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
430438
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
431439
submitted.* = state.total_submitted;
432440
completed.* = state.total_completed;

ffi/zig/src/lith_adapter.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,14 @@ pub const Reader = struct {
6868
pub fn readU64(self: Reader, off: usize) u64 {
6969
const abs = self.data_start + off;
7070
if (abs + 8 > self.seg_len) return 0;
71+
// SAFETY: bounds check on line above ensures abs + 8 <= seg_len; seg is a valid segment pointer from Reader.init
7172
return std.mem.readInt(u64, @as(*const [8]u8, @ptrCast(self.seg + abs)), .little);
7273
}
7374

7475
pub fn readU32(self: Reader, off: usize) u32 {
7576
const abs = self.data_start + off;
7677
if (abs + 4 > self.seg_len) return 0;
78+
// SAFETY: bounds check on line above ensures abs + 4 <= seg_len; seg is a valid segment pointer from Reader.init
7779
return std.mem.readInt(u32, @as(*const [4]u8, @ptrCast(self.seg + abs)), .little);
7880
}
7981

@@ -93,6 +95,7 @@ pub const Reader = struct {
9395
const ptr_off = self.ptr_start + ptr_idx * 8;
9496
if (ptr_off + 8 > self.seg_len) return &.{};
9597

98+
// SAFETY: bounds check on line 94 ensures ptr_off + 8 <= seg_len; seg is a valid Cap'n Proto segment pointer
9699
const raw = std.mem.readInt(u64, @as(*const [8]u8, @ptrCast(self.seg + ptr_off)), .little);
97100

98101
// Null pointer check.
@@ -127,6 +130,7 @@ pub const Reader = struct {
127130
const ptr_off = self.ptr_start + ptr_idx * 8;
128131
if (ptr_off + 8 > self.seg_len) return .{ .base = 0, .count = 0 };
129132

133+
// SAFETY: bounds check on line 128 ensures ptr_off + 8 <= seg_len; seg is a valid Cap'n Proto segment pointer
130134
const raw = std.mem.readInt(u64, @as(*const [8]u8, @ptrCast(self.seg + ptr_off)), .little);
131135
if (raw == 0) return .{ .base = 0, .count = 0 };
132136
if (raw & 3 != 1) return .{ .base = 0, .count = 0 };
@@ -151,6 +155,7 @@ pub const Reader = struct {
151155
const elem_ptr_off = list_base + elem_idx * 8;
152156
if (elem_ptr_off + 8 > self.seg_len) return &.{};
153157

158+
// SAFETY: bounds check on line 152 ensures elem_ptr_off + 8 <= seg_len; seg is a valid Cap'n Proto segment pointer
154159
const raw = std.mem.readInt(u64, @as(*const [8]u8, @ptrCast(self.seg + elem_ptr_off)), .little);
155160
if (raw == 0) return &.{};
156161
if (raw & 3 != 1) return &.{};

ffi/zig/src/ml_inference.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,12 +296,14 @@ const MlEngine = struct {
296296
export fn ddac_ml_init() ?*anyopaque {
297297
const allocator = std.heap.c_allocator;
298298
const engine = MlEngine.init(allocator) catch return null;
299+
// SAFETY: engine was just allocated by c_allocator.create(MlEngine), which returns a well-aligned *MlEngine
299300
return @ptrCast(engine);
300301
}
301302

302303
/// Free the ML inference engine.
303304
export fn ddac_ml_free(handle: ?*anyopaque) void {
304305
const ptr = handle orelse return;
306+
// SAFETY: ptr originates from ddac_ml_init() which stores a *MlEngine via @ptrCast; alignment is guaranteed by c_allocator
305307
const engine: *MlEngine = @ptrCast(@alignCast(ptr));
306308
engine.deinit();
307309
}
@@ -310,6 +312,7 @@ export fn ddac_ml_free(handle: ?*anyopaque) void {
310312
/// Returns 1 if available, 0 if not.
311313
export fn ddac_ml_available(handle: ?*anyopaque) u8 {
312314
const ptr = handle orelse return 0;
315+
// SAFETY: ptr originates from ddac_ml_init() which stores a *MlEngine via @ptrCast; alignment is guaranteed by c_allocator
313316
const engine: *MlEngine = @ptrCast(@alignCast(ptr));
314317
return if (engine.ort.available) 1 else 0;
315318
}
@@ -318,13 +321,15 @@ export fn ddac_ml_available(handle: ?*anyopaque) u8 {
318321
/// Returns: 0=TensorRT, 1=CUDA, 2=OpenVINO, 3=CPU
319322
export fn ddac_ml_provider(handle: ?*anyopaque) u8 {
320323
const ptr = handle orelse return 3;
324+
// SAFETY: ptr originates from ddac_ml_init() which stores a *MlEngine via @ptrCast; alignment is guaranteed by c_allocator
321325
const engine: *MlEngine = @ptrCast(@alignCast(ptr));
322326
return @intFromEnum(engine.ort.provider);
323327
}
324328

325329
/// Get human-readable name for the execution provider.
326330
export fn ddac_ml_provider_name(handle: ?*anyopaque) [*:0]const u8 {
327331
const ptr = handle orelse return "unavailable";
332+
// SAFETY: ptr originates from ddac_ml_init() which stores a *MlEngine via @ptrCast; alignment is guaranteed by c_allocator
328333
const engine: *MlEngine = @ptrCast(@alignCast(ptr));
329334
return switch (engine.ort.provider) {
330335
.tensorrt => "TensorRT (NVIDIA INT8/FP16)",
@@ -339,6 +344,7 @@ export fn ddac_ml_provider_name(handle: ?*anyopaque) [*:0]const u8 {
339344
/// layout_analysis.onnx, handwriting_ocr.onnx
340345
export fn ddac_ml_set_model_dir(handle: ?*anyopaque, dir: [*:0]const u8) void {
341346
const ptr = handle orelse return;
347+
// SAFETY: ptr originates from ddac_ml_init() which stores a *MlEngine via @ptrCast; alignment is guaranteed by c_allocator
342348
const engine: *MlEngine = @ptrCast(@alignCast(ptr));
343349
engine.setModelDir(std.mem.span(dir));
344350
}
@@ -359,6 +365,7 @@ export fn ddac_ml_run_stage(
359365
result_out.status = 4; // onnx_not_available
360366
return -1;
361367
};
368+
// SAFETY: ptr originates from ddac_ml_init() which stores a *MlEngine via @ptrCast; alignment is guaranteed by c_allocator
362369
const engine: *MlEngine = @ptrCast(@alignCast(ptr));
363370

364371
if (stage >= ML_STAGE_COUNT) {
@@ -384,6 +391,7 @@ export fn ddac_ml_stats(
384391
total_inference_us.* = 0;
385392
return;
386393
};
394+
// SAFETY: ptr originates from ddac_ml_init() which stores a *MlEngine via @ptrCast; alignment is guaranteed by c_allocator
387395
const engine: *MlEngine = @ptrCast(@alignCast(ptr));
388396
total_inferences.* = engine.total_inferences;
389397
total_inference_us.* = engine.total_inference_us;
@@ -402,5 +410,6 @@ export fn ddac_ml_stage_count() u8 {
402410
/// Get the model filename for a stage.
403411
export fn ddac_ml_model_name(stage: u8) [*:0]const u8 {
404412
if (stage >= ML_STAGE_COUNT) return "unknown";
413+
// SAFETY: MODEL_NAMES[stage] is a comptime string literal; .ptr is a valid [*]const u8 that is null-terminated by Zig's string literal guarantee
405414
return @ptrCast(MlEngine.MODEL_NAMES[stage].ptr);
406415
}

0 commit comments

Comments
 (0)