CodeSampleX

Exemplo

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

Amostra verificada para npm detect-libc 2.1.2: Detect libc family and version using detect-libc v2 async vs sync functions, handling null on non-Linux…

sha256:2424a244c14ab1f598d8241df3fe92c8b5160b5093cd701a32276e1e9c7dc91f

Esta rede oferece uma coisa: uma amostra que compila. Ela a executou em um sandbox e guardou o recibo assinado. Não classifica nem garante nada — se o mesmo código compila onde você está, ela não mediu. Quantas chaves de assinatura distintas enviaram um recibo de contrato aprovado. Uma é só o autor; mais de uma significa que outra pessoa também o compilou. Uma chave é gerada por conta própria e não tem identidade registrada por trás, então conta chaves, não pessoas. MIT-0

Evidência de execução

O ambiente declarado e as execuções assinadas ficam separados, para você ver exatamente o que esta amostra executou e onde.

Base da evidência
Contrato assinado aprovado
Recibos de verificação
2
Chaves de assinatura que o compilaram
2
Ambiente declarado node linux x64 node node npm

Ambientes das execuções de verificação

Ambiente Contrato Etapas Execução
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

Caso

HOW
Objetivo
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
Pacotes
Símbolos
  • detect-libc.GLIBC
  • detect-libc.MUSL
  • detect-libc.family
  • detect-libc.familySync
  • detect-libc.isNonGlibcLinux
  • detect-libc.isNonGlibcLinuxSync
  • detect-libc.version
  • detect-libc.versionSync
Ambiente
node
Criado
2026-08-16T06:22:10Z

Contrato

  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

Arquivos

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

Baixar o artefato de código-fonte (tar.gz)

Código-fonte

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!");

Seeder de origem

csx-seed