CodeSampleX

샘플

detect-libc 2.1.2: Detect libc family and version using detect-libc v2 async vs sync functions, handling null on non-Linux platforms, and avoiding v1 string property access traps

검증된 샘플 — npm detect-libc 2.1.2: Detect libc family and version using detect-libc v2 async vs sync functions, handling null on non-Linux platforms, and…

sha256:2424a244c14ab1f598d8241df3fe92c8b5160b5093cd701a32276e1e9c7dc91f

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
2
빌드한 서명 키
2
선언된 환경 node linux x64 node node npm

검증 실행 환경

환경 컨트랙트 단계 실행일
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-16
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18

케이스

HOW
목표
Detect libc family and version using detect-libc v2 async vs sync functions, handling null on non-Linux platforms, and avoiding v1 string property access traps
패키지
심벌
  • detect-libc.GLIBC
  • detect-libc.MUSL
  • detect-libc.family
  • detect-libc.familySync
  • detect-libc.isNonGlibcLinux
  • detect-libc.isNonGlibcLinuxSync
  • detect-libc.version
  • detect-libc.versionSync
환경
node
생성일
2026-08-16T06:22:10Z

컨트랙트

  1. assert detect-libc exports GLIBC and MUSL as lowercase string constants 'glibc' and 'musl'
  2. assert family, version, and isNonGlibcLinux are AsyncFunctions in v2 returning Promises, while familySync, versionSync, and isNonGlibcLinuxSync provide synchronous detection
  3. assert accessing detectLibc.family as a string property (v1 pattern) fails because typeof family is function, making equality comparisons with GLIBC or MUSL false
  4. assert evaluating detectLibc.isNonGlibcLinux as a boolean property (v1 pattern) is always truthy in JS, causing glibc Linux systems to mistakenly execute non-glibc code paths
  5. assert invoking detectLibc.family() without await returns a Promise instance, evaluating to false when compared against string constants and producing '[object Promise]' in template strings
  6. assert familySync() and versionSync() return null on non-Linux platforms (win32, darwin), rather than empty strings, 'unknown', or throwing, and isNonGlibcLinuxSync() returns false
  7. assert on Linux platforms, familySync() returns 'glibc', 'musl', or null, with isNonGlibcLinuxSync() returning true for musl and false for glibc
  8. assert exact parity between synchronous and asynchronous APIs where familySync() === await family(), versionSync() === await version(), and isNonGlibcLinuxSync() === await isNonGlibcLinux()
  9. assert the legacy v1 property method was removed in v2 and is undefined
  10. assert internal ELF interpreter parsing extracts PT_INTERP program headers from 64-bit ELF binaries to identify ld-musl- as MUSL and ld-linux- as GLIBC

파일

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • src/detect.mjs
  • test/contract.mjs

소스 아티팩트 내려받기 (tar.gz)

소스

NOTES.md
# Notes on detect-libc API Resolution and Migration Traps

## `search_known_solution` Result
- `search_known_solution` returned sample `sha256:de0de7aea8370a5ddbb611169fcb1bf333cb27e4a8318c0be8d18fb57c45682e` ("Run lightningcss on Alpine, where the native addon really does come in per-libc builds"), which focused on `lightningcss` multi-libc packaging and lockfile omissions rather than testing `detect-libc`'s own contract, async/sync APIs, return types, and breaking changes.

## What a Naive Model Would Write
- A naive model accesses `detectLibc.family` and `detectLibc.isNonGlibcLinux` as synchronous string/boolean properties (the v1 API) or invokes `detectLibc.family()` without `await` in synchronous native addon loaders, while expecting non-Linux platforms to return `""` or `"unknown"`.

## How the Wrong Version Fails
- In `detect-libc` v2, `family` and `isNonGlibcLinux` are async function references: equality comparisons like `detectLibc.family === detectLibc.GLIBC` fail silently by returning `false`, unawaited `family()` returns a `[object Promise]` object, and `if (detectLibc.isNonGlibcLinux)` evaluates to `true` on standard glibc Linux because function references are truthy, silently forcing glibc environments down musl binary paths until crashing with `ERR_DLOPEN_FAILED`.
csx.json
{"case":{"caseId":"case:sha256:23ce1330c94dc2365e832f160dcb42cf25436994000d67a5bbdd1bfc32b4356b","contract":["assert detect-libc exports GLIBC and MUSL as lowercase string constants 'glibc' and 'musl'","assert family, version, and isNonGlibcLinux are AsyncFunctions in v2 returning Promises, while familySync, versionSync, and isNonGlibcLinuxSync provide synchronous detection","assert accessing detectLibc.family as a string property (v1 pattern) fails because typeof family is function, making equality comparisons with GLIBC or MUSL false","assert evaluating detectLibc.isNonGlibcLinux as a boolean property (v1 pattern) is always truthy in JS, causing glibc Linux systems to mistakenly execute non-glibc code paths","assert invoking detectLibc.family() without await returns a Promise instance, evaluating to false when compared against string constants and producing '[object Promise]' in template strings","assert familySync() and versionSync() return null on non-Linux platforms (win32, darwin), rather than empty strings, 'unknown', or throwing, and isNonGlibcLinuxSync() returns false","assert on Linux platforms, familySync() returns 'glibc', 'musl', or null, with isNonGlibcLinuxSync() returning true for musl and false for glibc","assert exact parity between synchronous and asynchronous APIs where familySync() === await family(), versionSync() === await version(), and isNonGlibcLinuxSync() === await isNonGlibcLinux()","assert the legacy v1 property method was removed in v2 and is undefined","assert internal ELF interpreter parsing extracts PT_INTERP program headers from 64-bit ELF binaries to identify ld-musl- as MUSL and ld-linux- as GLIBC"],"goal":"Detect libc family and version using detect-libc v2 async vs sync functions, handling null on non-Linux platforms, and avoiding v1 string property access traps","kind":"HOW","packages":["pkg:npm/detect-libc@2.1.2"],"schemaVersion":1,"symbols":["detect-libc.GLIBC","detect-libc.MUSL","detect-libc.family","detect-libc.familySync","detect-libc.isNonGlibcLinux","detect-libc.isNonGlibcLinuxSync","detect-libc.version","detect-libc.versionSync"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/detect-libc@2.1.2"],"schemaVersion":1,"symbols":["detect-libc.GLIBC","detect-libc.MUSL","detect-libc.family","detect-libc.familySync","detect-libc.isNonGlibcLinux","detect-libc.isNonGlibcLinuxSync","detect-libc.version","detect-libc.versionSync"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-detect-libc-api",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-detect-libc-api",
      "version": "1.0.0",
      "dependencies": {
        "detect-libc": "2.1.2"
      }
    },
    "node_modules/detect-libc": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
      "license": "Apache-2.0",
      "engines": {
        "node": ">=8"
      }
    }
  }
}
package.json
{
  "name": "npm-detect-libc-api",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "dependencies": {
    "detect-libc": "2.1.2"
  }
}
src/detect.mjs
import { createRequire } from "node:module";
import path from "node:path";
import detectLibc from "detect-libc";

const require = createRequire(import.meta.url);

/**
 * Re-export constants and APIs from detect-libc.
 */
export const {
  GLIBC,
  MUSL,
  family,
  familySync,
  isNonGlibcLinux,
  isNonGlibcLinuxSync,
  version,
  versionSync,
} = detectLibc;

/**
 * Builds the canonical native addon package suffix or triplet for the current platform.
 *
 * In detect-libc v2:
 * 1. Synchronous loaders MUST call familySync(), not the unawaited family() (which returns a Promise).
 * 2. On non-Linux platforms (Windows, macOS), familySync() returns null, NOT "" or "unknown".
 * 3. On Linux, familySync() returns "glibc" (GLIBC) or "musl" (MUSL).
 *
 * @param {object} [env] - Optional override for platform / arch / family
 * @returns {string} - e.g. "linux-x64-gnu", "linux-x64-musl", "win32-x64-msvc", "darwin-arm64"
 */
export function resolvePlatformTriplet(env = {}) {
  const platform = env.platform ?? process.platform;
  const arch = env.arch ?? process.arch;
  const detectedFamily = env.family !== undefined ? env.family : detectLibc.familySync();

  const parts = [platform, arch];

  if (platform === "linux") {
    if (detectedFamily === detectLibc.MUSL) {
      parts.push("musl");
    } else if (arch === "arm") {
      parts.push("gnueabihf");
    } else {
      // Default / standard glibc linux
      parts.push("gnu");
    }
  } else if (platform === "win32") {
    parts.push("msvc");
  }

  return parts.join("-");
}

/**
 * Demonstrates the common v1 -> v2 migration traps where competent models
 * write code that fails silently or loudly.
 */
export function evaluateNaiveAssumptions() {
  // Trap 1: detectLibc.family in v1 was a string ("glibc" | "musl" | "").
  // In v2, detectLibc.family is an AsyncFunction (async () => Promise<string|null>).
  const v1PropertyStyleComparison = (detectLibc.family === detectLibc.GLIBC); // ALWAYS false in v2!

  // Trap 2: detectLibc.isNonGlibcLinux in v1 was a boolean property (false on glibc).
  // In v2, it is an AsyncFunction. Checking `if (detectLibc.isNonGlibcLinux)` is ALWAYS truthy!
  const isFunctionTruthy = Boolean(detectLibc.isNonGlibcLinux); // ALWAYS true in JS!

  // Trap 3: Calling async family() synchronously without await returns a Promise object.
  const unawaitedFamilyCall = detectLibc.family();
  const unawaitedComparison = (unawaitedFamilyCall === detectLibc.GLIBC); // ALWAYS false!

  // Trap 4: detectLibc.method was removed in v2.
  const methodProperty = detectLibc.method; // undefined in v2

  return {
    v1PropertyStyleComparison,
    isFunctionTruthy,
    unawaitedIsPromise: unawaitedFamilyCall instanceof Promise,
    unawaitedComparison,
    methodProperty,
    familyType: typeof detectLibc.family,
    isNonGlibcLinuxType: typeof detectLibc.isNonGlibcLinux,
    versionType: typeof detectLibc.version,
  };
}

/**
 * Minimal 64-bit ELF parser mirroring detect-libc's internal elf.js interpreterPath extraction.
 * Extracts PT_INTERP (type 3) program header string.
 *
 * @param {Buffer} elf - ELF binary buffer
 * @returns {string|null} - Interpreter path, e.g. "/lib/ld-musl-x86_64.so.1"
 */
export function extractElfInterpreter(elf) {
  if (!Buffer.isBuffer(elf) || elf.length < 64) {
    return null;
  }
  // Magic bytes: 0x7F 'E' 'L' 'F'
  if (elf.readUInt32BE(0) !== 0x7f454c46) {
    return null;
  }
  // 64-bit ELF check (byte 4 == 2)
  if (elf.readUInt8(4) !== 2) {
    return null;
  }
  // Little-endian check (byte 5 == 1)
  if (elf.readUInt8(5) !== 1) {
    return null;
  }

  const offset = elf.readUInt32LE(32);
  const size = elf.readUInt16LE(54);
  const count = elf.readUInt16LE(56);

  for (let i = 0; i < count; i++) {
    const headerOffset = offset + (i * size);
    const type = elf.readUInt32LE(headerOffset);
    if (type === 3) { // PT_INTERP
      const fileOffset = elf.readUInt32LE(headerOffset + 8);
      const fileSize = elf.readUInt32LE(headerOffset + 32);
      return elf.subarray(fileOffset, fileOffset + fileSize).toString("utf8").replace(/\0.*$/g, "");
    }
  }

  return null;
}

/**
 * Creates a minimal synthetic 64-bit ELF buffer with a PT_INTERP entry for testing libc resolution.
 *
 * @param {string} interpPath - Interpreter path string
 * @returns {Buffer} - Synthetic ELF buffer
 */
export function createSyntheticElf(interpPath) {
  const buf = Buffer.alloc(256);
  // Magic: 0x7F, 'E', 'L', 'F'
  buf.writeUInt8(0x7f, 0);
  buf.write("ELF", 1, "ascii");
  buf.writeUInt8(2, 4); // 64-bit
  buf.writeUInt8(1, 5); // Little endian
  buf.writeUInt8(1, 6); // Version 1

  const phOffset = 64;
  const phSize = 56;
  const phCount = 1;
  buf.writeUInt32LE(phOffset, 32); // e_phoff (low 32-bit)
  buf.writeUInt16LE(phSize, 54);   // e_phentsize
  buf.writeUInt16LE(phCount, 56);  // e_phnum

  // Program header 0: PT_INTERP (type 3)
  const interpOffset = 140;
  const interpBytes = Buffer.from(interpPath + "\0", "utf8");
  interpBytes.copy(buf, interpOffset);

  buf.writeUInt32LE(3, phOffset);                     // p_type = PT_INTERP (3)
  buf.writeUInt32LE(interpOffset, phOffset + 8);       // p_offset (low 32-bit)
  buf.writeUInt32LE(interpBytes.length, phOffset + 32); // p_filesz (low 32-bit)

  return buf;
}
test/contract.mjs
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import detectLibc, {
  GLIBC,
  MUSL,
  family,
  familySync,
  isNonGlibcLinux,
  isNonGlibcLinuxSync,
  version,
  versionSync,
} from "detect-libc";

import {
  resolvePlatformTriplet,
  evaluateNaiveAssumptions,
  extractElfInterpreter,
  createSyntheticElf,
} from "../src/detect.mjs";

const require = createRequire(import.meta.url);
const detectCjs = require("detect-libc");

// --- 1. Export Shape & Constant Values ---
// Constants are lowercase strings 'glibc' and 'musl'
assert.equal(GLIBC, "glibc");
assert.equal(MUSL, "musl");
assert.equal(detectLibc.GLIBC, "glibc");
assert.equal(detectLibc.MUSL, "musl");
assert.equal(detectCjs.GLIBC, "glibc");
assert.equal(detectCjs.MUSL, "musl");

// v1 property 'method' was removed in v2
assert.equal(detectLibc.method, undefined);
assert.equal(detectCjs.method, undefined);

// --- 2. Function Signature & v1 -> v2 Migration Traps ---
// In detect-libc v2, family, version, and isNonGlibcLinux are async functions returning Promises.
assert.equal(typeof family, "function");
assert.equal(typeof version, "function");
assert.equal(typeof isNonGlibcLinux, "function");

// Synchronous counterparts have 'Sync' suffix
assert.equal(typeof familySync, "function");
assert.equal(typeof versionSync, "function");
assert.equal(typeof isNonGlibcLinuxSync, "function");

// Naive Trap: Comparing detectLibc.family to a string constant (v1 API style)
// In v1, detectLibc.family was a string property. In v2, it is a function.
assert.notEqual(detectLibc.family, "glibc");
assert.notEqual(detectLibc.family, "musl");
assert.equal(detectLibc.family === detectLibc.GLIBC, false);
assert.equal(detectLibc.family === detectLibc.MUSL, false);

// Naive Trap: Boolean truthiness of isNonGlibcLinux function object (v1 API style)
// In v1, detectLibc.isNonGlibcLinux was a boolean property (false on glibc).
// In v2, isNonGlibcLinux is a function reference, so `Boolean(isNonGlibcLinux)` is ALWAYS true!
// Code expecting v1 semantics will mistakenly treat GLIBC Linux as non-glibc!
const naiveAssumptions = evaluateNaiveAssumptions();
assert.equal(naiveAssumptions.isFunctionTruthy, true);
assert.equal(naiveAssumptions.v1PropertyStyleComparison, false);
assert.equal(naiveAssumptions.unawaitedIsPromise, true);
assert.equal(naiveAssumptions.unawaitedComparison, false);
assert.equal(naiveAssumptions.methodProperty, undefined);

// Naive Trap: Unawaited async family() call returns a Promise, not a string
const unawaited = family();
assert.equal(unawaited instanceof Promise, true);
assert.equal(typeof unawaited, "object");
assert.equal(unawaited === GLIBC, false);
assert.equal(unawaited === MUSL, false);
// Stringifying unawaited promise produces "[object Promise]" rather than libc name
assert.equal(`${unawaited}`, "[object Promise]");

// --- 3. Synchronous & Asynchronous API Parity ---
const syncFam = familySync();
const asyncFam = await family();
assert.equal(syncFam, asyncFam, "familySync() must match await family()");

const syncVer = versionSync();
const asyncVer = await version();
assert.equal(syncVer, asyncVer, "versionSync() must match await version()");

const syncNonGlibc = isNonGlibcLinuxSync();
const asyncNonGlibc = await isNonGlibcLinux();
assert.equal(syncNonGlibc, asyncNonGlibc, "isNonGlibcLinuxSync() must match await isNonGlibcLinux()");

// --- 4. Platform-Dependent Behavior Contract ---
if (process.platform === "linux") {
  // On Linux: familySync() resolves to GLIBC or MUSL (or null on obscure libc)
  assert.ok(
    syncFam === GLIBC || syncFam === MUSL || syncFam === null,
    `On Linux, familySync() returned unexpected value: ${syncFam}`,
  );

  if (syncFam === MUSL) {
    assert.equal(syncNonGlibc, true, "MUSL systems must have isNonGlibcLinuxSync() === true");
  } else if (syncFam === GLIBC) {
    assert.equal(syncNonGlibc, false, "GLIBC systems must have isNonGlibcLinuxSync() === false");
  }

  // Version on Linux is a string (e.g. "2.35" or "1.2.3") when resolvable
  if (syncVer !== null) {
    assert.equal(typeof syncVer, "string");
    assert.match(syncVer, /^\d+(\.\d+)*$/);
  }
} else {
  // On Non-Linux (win32, darwin, etc.):
  // detect-libc v2 returns null (NOT "" empty string as v1 did, and NOT "unknown")
  assert.equal(syncFam, null, "Non-Linux platforms must return null for familySync()");
  assert.notStrictEqual(syncFam, "", "detect-libc v2 must not return empty string on non-Linux");
  assert.notStrictEqual(syncFam, "unknown", "detect-libc v2 must not return 'unknown' on non-Linux");

  assert.equal(syncVer, null, "Non-Linux platforms must return null for versionSync()");
  assert.equal(syncNonGlibc, false, "Non-Linux platforms must return false for isNonGlibcLinuxSync()");
}

// --- 5. Addon Triplet Resolver Helper Validation ---
// Verify proper platform triplet synthesis for various environments
assert.equal(
  resolvePlatformTriplet({ platform: "linux", arch: "x64", family: MUSL }),
  "linux-x64-musl",
);
assert.equal(
  resolvePlatformTriplet({ platform: "linux", arch: "x64", family: GLIBC }),
  "linux-x64-gnu",
);
assert.equal(
  resolvePlatformTriplet({ platform: "linux", arch: "arm", family: GLIBC }),
  "linux-arm-gnueabihf",
);
assert.equal(
  resolvePlatformTriplet({ platform: "win32", arch: "x64", family: null }),
  "win32-x64-msvc",
);
assert.equal(
  resolvePlatformTriplet({ platform: "darwin", arch: "arm64", family: null }),
  "darwin-arm64",
);

// --- 6. ELF Interpreter Parsing Mechanism ---
// detect-libc inspects /proc/self/exe to read the PT_INTERP program header.
// Validate ELF parser against synthetic ELF binaries:
const muslElf = createSyntheticElf("/lib/ld-musl-x86_64.so.1");
const muslInterp = extractElfInterpreter(muslElf);
assert.equal(muslInterp, "/lib/ld-musl-x86_64.so.1");
assert.ok(muslInterp.includes("/ld-musl-"), "Musl interpreter must contain /ld-musl-");

const glibcElf = createSyntheticElf("/lib64/ld-linux-x86-64.so.2");
const glibcInterp = extractElfInterpreter(glibcElf);
assert.equal(glibcInterp, "/lib64/ld-linux-x86-64.so.2");
assert.ok(glibcInterp.includes("/ld-linux-"), "Glibc interpreter must contain /ld-linux-");

// Invalid or truncated buffers return null safely
assert.equal(extractElfInterpreter(Buffer.from("invalid-elf-header")), null);
assert.equal(extractElfInterpreter(Buffer.alloc(32)), null);

console.log("Contract verified successfully!");

오리진 시더

csx-seed