{"record":{"id":"8b376eb03f7350ff","repo":"dmtrKovalenko/fff","slug":"fff-search-mixed-returned-null-search-result","errorCode":null,"errorMessage":"fff_search_mixed returned null search result","messagePattern":"fff_search_mixed returned null search result","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/fff-bun/src/ffi.ts","lineNumber":908,"sourceCode":"      modificationFrecencyScore: Number(read.i64(pp, MI_MODFR)),\n      totalFrecencyScore: Number(read.i64(pp, MI_TOTAL_FR)),\n    },\n  };\n}\n\n/**\n * Parse an FffMixedSearchResult from a raw FffResult pointer, then free native memory.\n */\nfunction parseMixedSearchResult(resultPtr: Pointer | null): Result<MixedSearchResult> {\n  if (resultPtr === null) {\n    return err(\"FFI returned null pointer\");\n  }\n\n  const envelope = readResultEnvelope(resultPtr);\n  if (!(\"success\" in envelope)) return envelope;\n\n  if (envelope.handlePtr === 0) {\n    return err(\"fff_search_mixed returned null search result\");\n  }\n\n  const hp = asPtr(envelope.handlePtr);\n  const count = read.u32(hp, MSR_COUNT);\n  const totalMatched = read.u32(hp, MSR_MATCHED);\n  const totalFiles = read.u32(hp, MSR_TOTAL_FILES);\n  const totalDirs = read.u32(hp, MSR_TOTAL_DIRS);\n\n  // Read location\n  const locTag = read.u8(hp, MSR_LOC_TAG);\n  let location: Location | undefined;\n  if (locTag === 1) {\n    location = { type: \"line\", line: read.i32(hp, MSR_LOC_LINE) };\n  } else if (locTag === 2) {\n    location = {\n      type: \"position\",\n      line: read.i32(hp, MSR_LOC_LINE),\n      col: read.i32(hp, MSR_LOC_COL),","sourceCodeStart":890,"sourceCodeEnd":926,"githubUrl":"https://github.com/dmtrKovalenko/fff/blob/7f8537e70f0ea1210f9acbbfc4640141105cdc78/packages/fff-bun/src/ffi.ts#L890-L926","documentation":"The FFI envelope from fff_search_mixed reported success, but its result handle pointer is 0 — meaning the native side returned a null search result even though the call 'succeeded'. The binding treats this as a contract violation and returns an error rather than fabricating an empty result.","triggerScenarios":"ffiSearchMixed where the native fff_search_mixed produced a success envelope with handlePtr === 0, e.g. native ran with an uninitialized/garbage-collected handle or the native call failed to allocate the result struct.","commonSituations":"Calling search on a FileFinder whose native instance was already destroyed (double-free / use-after-free scenario); native OOM while allocating the result; a bug in a mismatched native library version.","solutions":["Ensure the FileFinder instance is alive (not destroyed) when calling mixed search — check handle validity first.","Rebuild/reinstall the native library to rule out version mismatch with the JS bindings.","If reproducible on a healthy instance, report it: a success envelope with a null handle is a native-side bug."],"exampleFix":"// before\nconst r = ffiSearchMixed(handle, query); // handle may be stale\n// after\nconst alive = ensureAlive(handle);\nif (!alive.ok) return alive;\nconst r = ffiSearchMixed(alive.value, query);","handlingStrategy":"try-catch","validationCode":"if (finder === null || !finder.isAlive()) return err('finder destroyed');","typeGuard":"function isAlive(finder) { return finder != null && finder.handle !== null; }","tryCatchPattern":"try { const r = await finder.searchMixed(q); } catch (e) { if (e.message.includes('null search result')) { recreateFinder(); } }","preventionTips":["Guard all FFI calls with an aliveness check.","Route all native calls through one owner that tracks instance lifetime.","Upgrade native + bindings together to avoid envelope/handle ABI drift."],"tags":["ffi","null-pointer","bun","search"],"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"}