dmtrKovalenko/fff · error

ignore_count > 0 but ignore is NULL

Error message

ignore_count > 0 but ignore is NULL

What it means

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.

Solutions

  1. Set opts.ignore to the pointer of the allocated char** array whenever ignore_count > 0.
  2. If you have no ignore patterns, set both ignore_count = 0 and ignore = NULL.
  3. Keep the ignore array alive until fff_watch returns (pin it / hold the reference) so the pointer is not nulled by GC.

Example fix

// before
let opts = FfiWatchOptions { ignore_count: patterns.len() as u32, ignore: null() };
// after
let opts = FfiWatchOptions { ignore_count: patterns.len() as u32, ignore: patternsPtr };
Defensive patterns

Strategy: validation

Validate before calling

function validateWatchOptions(opts) {
  if (opts.ignore_count > 0 && (opts.ignore === null || opts.ignore === undefined)) {
    throw new Error('ignore array required when ignore_count > 0');
  }
}

Type guard

function hasIgnoreArray(opts) { return opts.ignore_count === 0 || (opts.ignore !== null && opts.ignore !== undefined); }

Try / catch

try { startWatch(opts); } catch (e) { if (String(e).includes('ignore is NULL')) console.error('FfiWatchOptions built incorrectly: pointer/count mismatch'); }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of dmtrKovalenko/fff@7f8537e70f (2026-09-10). Data as JSON: /api/errors/90dc1f1b15fa5b76. Report an issue: GitHub.

Appendix: source

Thrown at crates/fff-c/src/watch.rs:92

unsafe fn watch_options_from_ffi(
    opts: *const FffWatchOptions,
) -> Result<WatchOptions, *mut FffResult> {
    if opts.is_null() {
        return Ok(WatchOptions::default());
    }
    let opts = unsafe { &*opts };
    if opts.version == 0 || opts.version > FFF_WATCH_OPTIONS_VERSION {
        return Err(FffResult::err(&format!(
            "Unsupported FffWatchOptions version {} (library understands up to {})",
            opts.version, FFF_WATCH_OPTIONS_VERSION
        )));
    }

    let mut ignore = Vec::with_capacity(opts.ignore_count as usize);
    if opts.ignore_count > 0 {
        if opts.ignore.is_null() {
            return Err(FffResult::err("ignore_count > 0 but ignore is NULL"));
        }
        for i in 0..opts.ignore_count as usize {
            let entry = unsafe { *opts.ignore.add(i) };
            match unsafe { crate::cstr_to_str(entry) } {
                Some(s) if !s.is_empty() => ignore.push(s.to_string()),
                Some(_) => {}
                None => return Err(FffResult::err("ignore entry is NULL or invalid UTF-8")),
            }
        }
    }

    Ok(WatchOptions { ignore })
}

// The caller guarantees user_data is safe on the callback thread.
struct UserData(*mut c_void);
unsafe impl Send for UserData {}
unsafe impl Sync for UserData {}

View on GitHub (pinned to 7f8537e70f)