fix(node): reject musl binaries with unresolved AVX-512 symbol

This commit is contained in:
Gatefixer
2026-08-05 18:51:18 +00:00
parent c7ea91f3ea
commit a23ee5fdac
4 changed files with 127 additions and 1 deletions
+1
View File
@@ -15,6 +15,7 @@ renovate.json
src
lancedb
examples
scripts
nodejs-artifacts
Cargo.toml
biome.json
+47
View File
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
const {
checkNativeBinary,
findForbiddenUndefinedSymbols,
} = require("../scripts/check-native-symbols.js");
test("detects the unresolved AVX-512 symbol from broken musl binaries", () => {
const output = [
" U napi_create_function",
" U sum_4bit_dist_table_32bytes_batch_avx512",
" U strlen",
].join("\n");
expect(findForbiddenUndefinedSymbols(output)).toEqual([
"sum_4bit_dist_table_32bytes_batch_avx512",
]);
});
test("accepts native binaries without unresolved internal Lance symbols", () => {
const runNm = jest.fn(() => ({
status: 0,
stdout:
" U napi_create_function\n U strlen\n",
stderr: "",
}));
expect(() => checkNativeBinary("lancedb.node", runNm)).not.toThrow();
expect(runNm).toHaveBeenCalledWith(
"nm",
["-D", "--undefined-only", "lancedb.node"],
{ encoding: "utf8" },
);
});
test("rejects native binaries with the unresolved AVX-512 symbol", () => {
const runNm = jest.fn(() => ({
status: 0,
stdout: " U sum_4bit_dist_table_32bytes_batch_avx512\n",
stderr: "",
}));
expect(() => checkNativeBinary("lancedb.node", runNm)).toThrow(
"lancedb.node contains unresolved internal Lance symbols: sum_4bit_dist_table_32bytes_batch_avx512",
);
});
+2 -1
View File
@@ -86,7 +86,8 @@
"postdocs": "node typedoc_post_process.js",
"lint": "biome check . && biome format .",
"lint-fix": "biome check --write . && biome format --write .",
"prepublishOnly": "napi prepublish -t npm",
"check:native-symbols": "node scripts/check-native-symbols.js",
"prepublishOnly": "pnpm check:native-symbols && napi prepublish -t npm",
"test": "jest --verbose",
"integration": "S3_TEST=1 pnpm test",
"universal": "napi universalize",
+77
View File
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
const { spawnSync } = require("node:child_process");
const { existsSync } = require("node:fs");
const path = require("node:path");
const FORBIDDEN_UNDEFINED_SYMBOLS = new Set([
"sum_4bit_dist_table_32bytes_batch_avx512",
]);
function findForbiddenUndefinedSymbols(output) {
const found = new Set();
for (const line of output.split(/\r?\n/)) {
const columns = line.trim().split(/\s+/);
const symbol = columns.at(-1)?.split("@")[0];
if (symbol && FORBIDDEN_UNDEFINED_SYMBOLS.has(symbol)) {
found.add(symbol);
}
}
return [...found].sort();
}
function checkNativeBinary(binaryPath, runNm = spawnSync) {
const result = runNm("nm", ["-D", "--undefined-only", binaryPath], {
encoding: "utf8",
});
if (result.error) {
throw new Error(`Unable to inspect ${binaryPath}: ${result.error.message}`);
}
if (result.status !== 0) {
throw new Error(
`Unable to inspect ${binaryPath}: nm exited with status ${result.status}\n${result.stderr}`,
);
}
const forbidden = findForbiddenUndefinedSymbols(result.stdout);
if (forbidden.length > 0) {
throw new Error(
`${binaryPath} contains unresolved internal Lance symbols: ${forbidden.join(
", ",
)}`,
);
}
}
function main() {
const binaryPath = path.resolve(
__dirname,
"..",
"npm",
"linux-x64-musl",
"lancedb.linux-x64-musl.node",
);
if (!existsSync(binaryPath)) {
throw new Error(
`Missing ${binaryPath}; assemble the native artifacts before publishing`,
);
}
checkNativeBinary(binaryPath);
}
if (require.main === module) {
try {
main();
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
}
}
module.exports = { checkNativeBinary, findForbiddenUndefinedSymbols };