tursodatabase/turso · critical · SystemExit

self-test failed: missing export was not reported

Error message

self-test failed: missing export was not reported

What it means

The second half of the script's --self-test: it checks that a deliberately-absent symbol IS reported as missing. This error means missing_exports() returned an empty list for the fake symbol 'turso_sync_database_missing', i.e. the checker falsely claimed an export exists. The detection logic is over-matching and would silently pass broken libraries in CI.

Source

Thrown at bindings/dotnet/scripts/check_elf_dynsym_exports.py:40

    path = Path(sys.argv[1])
    needed = sys.argv[2:]
    missing = missing_exports(path, needed)
    if missing:
        raise SystemExit(f"{path} is missing dynsym exports: {', '.join(missing)}")


def run_self_test() -> None:
    import tempfile

    symbol = "turso_sync_database_new"
    with tempfile.NamedTemporaryFile(delete=False, suffix=".so") as handle:
        handle.write(elf64_with_dynsym(symbol))
        path = Path(handle.name)
    try:
        if missing_exports(path, [symbol]):
            raise SystemExit("self-test failed: expected export was not found")
        if not missing_exports(path, ["turso_sync_database_missing"]):
            raise SystemExit("self-test failed: missing export was not reported")
    finally:
        path.unlink()


def missing_exports(path: Path, needed: list[str]) -> list[str]:
    names = dynsym_names(path)
    return [symbol for symbol in needed if symbol not in names]


def elf64_with_dynsym(symbol: str) -> bytes:
    shstrtab = b"\0.shstrtab\0.dynstr\0.dynsym\0"
    dynstr = b"\0" + symbol.encode("ascii") + b"\0"
    dynsym = bytes(24) + struct.pack("<IBBHQQ", 1, 0x12, 0, 1, 0x1000, 8)

    shstrtab_offset = 64
    dynstr_offset = shstrtab_offset + len(shstrtab)
    dynsym_offset = dynstr_offset + len(dynstr)
    dynsym_pad = (8 - (dynsym_offset % 8)) % 8

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Inspect missing_exports() — ensure it filters with `symbol not in names` exactly
  2. Inspect names_from_dynsym() to confirm it only adds non-empty, actually-present dynsym names
  3. Check that the synthetic ELF's dynstr/dynsym only contain the one intended symbol
  4. Re-run --self-test after the fix to confirm both positive and negative cases pass

Example fix

// before
return [symbol for symbol in needed if symbol in names]  # inverted
// after
return [symbol for symbol in needed if symbol not in names]
Defensive patterns

Strategy: try-catch

Validate before calling

result = subprocess.run(["check_elf_dynsym_exports.py", "--self-test"], check=True)

Try / catch

try:
    subprocess.run(["check_elf_dynsym_exports.py", "--self-test"], check=True)
except subprocess.CalledProcessError:
    # negative case broken: audit membership logic in missing_exports()
    raise

Prevention

When it happens

Trigger: Running `check_elf_dynsym_exports.py --self-test` when `not missing_exports(path, ["turso_sync_database_missing"])` is False — the parser returned a symbol set that contains names it should not, or membership logic is inverted.

Common situations: A refactor of names_from_dynsym() or missing_exports() inverted a condition, the name decoding started emitting placeholder names, or someone changed the membership check from `symbol not in names` to something too permissive (e.g. prefix matching).

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-09-13). Data as JSON: /api/errors/1303cf93da3dfeb8. Report an issue: GitHub.