Skip to content

Two native pg implementations exported the same extern "C" symbols, resolved by link order — audit the perry-ext-*/perry-stdlib pairs and gate duplicate symbol exports #10678

Description

@proggeramlug

Found while removing the pg native binding (PR #10677): there were two native pg implementations, both
defining the same extern "C" symbols, and link order silently decided which one was live.

Nothing reported this. There is no gate that notices two crates exporting one extern "C" surface; the linker
just picks one. Whichever it picked is what users got, and a fix applied to the other one would have looked
inert — the same failure shape as the stale-archive trap CLAUDE.md documents, but caused by duplication rather
than staleness.

This is probably not unique to pg

mysql2 shows the same two-location shape: crates/perry-stdlib/src/mysql2/ (~1870 lines) alongside a
crates/perry-ext-mysql2 crate. That one is being untangled now as part of its own removal, but the pattern
suggests the #466 migration (moving bindings from perry-stdlib into perry-ext-* crates) left duplicates behind
in more than one place.

There are currently ~38 crates/perry-ext-* crates. Any of them whose package also has a crates/perry-stdlib/src/<pkg>/
directory is a candidate.

Suggested work

  1. Enumerate it. For each perry-ext-* crate, check whether a same-named perry-stdlib/src/ module exists, and
    whether the two export overlapping extern "C" symbols.
  2. Add a gate. A duplicate #[no_mangle]/extern "C" symbol across crates should be a build or lint failure,
    not a link-order coin flip. This repo already ratchets several similar properties (addr-class, raw-handle,
    unrooted-local-shape), so the machinery exists.
  3. Delete the dead side of each pair. Where both are live-ish, decide which is canonical before the removal
    campaign reaches that package — otherwise a removal PR deletes one and leaves the other quietly serving traffic.

Why it matters for the current work

The owner's goal is to delete these hand-written Rust reimplementations in favour of compiling the real npm
packages. A removal that deletes only the registered copy would leave a shadow implementation in the tree that
still claims the symbols — the package would appear to be "removed" while a duplicate continued to answer. #10677
avoided that only because its agent went looking.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions