{"record":{"id":"90dc1f1b15fa5b76","repo":"dmtrKovalenko/fff","slug":"ignore-count-0-but-ignore-is-null","errorCode":null,"errorMessage":"ignore_count > 0 but ignore is NULL","messagePattern":"ignore_count > 0 but ignore is NULL","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fff-c/src/watch.rs","lineNumber":92,"sourceCode":"\nunsafe fn watch_options_from_ffi(\n    opts: *const FffWatchOptions,\n) -> Result<WatchOptions, *mut FffResult> {\n    if opts.is_null() {\n        return Ok(WatchOptions::default());\n    }\n    let opts = unsafe { &*opts };\n    if opts.version == 0 || opts.version > FFF_WATCH_OPTIONS_VERSION {\n        return Err(FffResult::err(&format!(\n            \"Unsupported FffWatchOptions version {} (library understands up to {})\",\n            opts.version, FFF_WATCH_OPTIONS_VERSION\n        )));\n    }\n\n    let mut ignore = Vec::with_capacity(opts.ignore_count as usize);\n    if opts.ignore_count > 0 {\n        if opts.ignore.is_null() {\n            return Err(FffResult::err(\"ignore_count > 0 but ignore is NULL\"));\n        }\n        for i in 0..opts.ignore_count as usize {\n            let entry = unsafe { *opts.ignore.add(i) };\n            match unsafe { crate::cstr_to_str(entry) } {\n                Some(s) if !s.is_empty() => ignore.push(s.to_string()),\n                Some(_) => {}\n                None => return Err(FffResult::err(\"ignore entry is NULL or invalid UTF-8\")),\n            }\n        }\n    }\n\n    Ok(WatchOptions { ignore })\n}\n\n// The caller guarantees user_data is safe on the callback thread.\nstruct UserData(*mut c_void);\nunsafe impl Send for UserData {}\nunsafe impl Sync for UserData {}","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/dmtrKovalenko/fff/blob/7f8537e70f0ea1210f9acbbfc4640141105cdc78/crates/fff-c/src/watch.rs#L74-L110","documentation":"fff_watch's FFI options struct carries an ignore list as (pointer, count) pair. If ignore_count is non-zero but the ignore pointer is NULL, the C API cannot read any entries, so watch_options_from_ffi rejects the options. This guards against a mismatched or zero-initialized WatchOptions struct produced by an FFI caller.","triggerScenarios":"Calling fff_watch (directly or via fff_watch_args / bun ffiSetWatch) with an FfiWatchOptions where ignore_count > 0 but ignore is NULL — e.g. struct zero-initialized, count set manually, pointer field forgotten, or GC collected the array before the call.","commonSituations":"Developers hand-building the FFI struct in C/Rust FFI bindings set `count` from an array length but forget to assign the array pointer; or use a zeroed struct with `#[repr(C)]` and only fill ignore_count.","solutions":["Set opts.ignore to the pointer of the allocated char** array whenever ignore_count > 0.","If you have no ignore patterns, set both ignore_count = 0 and ignore = NULL.","Keep the ignore array alive until fff_watch returns (pin it / hold the reference) so the pointer is not nulled by GC."],"exampleFix":"// before\nlet opts = FfiWatchOptions { ignore_count: patterns.len() as u32, ignore: null() };\n// after\nlet opts = FfiWatchOptions { ignore_count: patterns.len() as u32, ignore: patternsPtr };","handlingStrategy":"validation","validationCode":"function validateWatchOptions(opts) {\n  if (opts.ignore_count > 0 && (opts.ignore === null || opts.ignore === undefined)) {\n    throw new Error('ignore array required when ignore_count > 0');\n  }\n}","typeGuard":"function hasIgnoreArray(opts) { return opts.ignore_count === 0 || (opts.ignore !== null && opts.ignore !== undefined); }","tryCatchPattern":"try { startWatch(opts); } catch (e) { if (String(e).includes('ignore is NULL')) console.error('FfiWatchOptions built incorrectly: pointer/count mismatch'); }","preventionTips":["Use a constructor/builder for FfiWatchOptions that keeps pointer and count in sync.","Pass 0/NULL together when there is no ignore list.","Pin the ignore array in memory until the watch call returns."],"tags":["ffi","null-pointer","watch","validation"],"backgroundTag":"null-argument","analyzedSha":"7f8537e70f0ea1210f9acbbfc4640141105cdc78","analyzedAt":"2026-09-10T07:26:53.407Z","contentChangedAt":"2026-09-10T07:26:53.407Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}