Skip to content

__attribute__((weak)) declarations are emitted as ordinary externs, producing strong undefined references on ELF #3402

Description

@lwz23

Input C/C++ Header

int api_version(void);
int optional_api(int value) __attribute__((weak));

The first declaration makes sure the test still depends on the shared library
when optional_api is missing. The weak function is used in the usual way:

return optional_api ? optional_api(value) : value + 100;

Bindgen Invocation

$ bindgen weak_api.h \
    --rust-target 1.75 \
    --no-layout-tests \
    --no-rustfmt-bindings \
    --output bindings.rs \
    -- -I.

Actual Results

The relevant output, reformatted for readability, is:

extern "C" {
    pub fn api_version() -> ::std::os::raw::c_int;
}
extern "C" {
    pub fn optional_api(
        value: ::std::os::raw::c_int,
    ) -> ::std::os::raw::c_int;
}

optional_api is represented like an ordinary required function. With bindgen
0.72.1 and rustc 1.75, the C and Rust callers lower to:

; C
declare extern_weak i32 @optional_api(i32 noundef)

; Rust, using bindings.rs unchanged
declare noundef i32 @optional_api(i32 noundef) unnamed_addr

A source build from current main at
9d26c6eddeff9192ddedb563192abe3128fc5aae produced the same ordinary Rust
declaration. In that run, the ELF symbol tables contain:

C caller:    WEAK   UND optional_api
Rust caller: GLOBAL UND optional_api

The full reproducer is
weak-function-linkage-repro.zip.
It builds an old library that exports only api_version, and a new library
that also exports optional_api:

$ unzip weak-function-linkage-repro.zip
$ cd repro
$ BINDGEN=/path/to/bindgen ./run.sh

Both the 0.72.1 and current-main runs gave these results:

C linked to old:                 version=1 fallback=105
C linked to new:                 version=2 optional=205
Rust linked to new:              version=2 optional=205
Rust linked directly to old:     link error, undefined reference to optional_api
C linked to new, run with old:   version=1 fallback=105
Rust linked to new, run with old: exit 127, undefined symbol: optional_api

The C caller can run with the old library and take its fallback path. The Rust
caller using the unchanged generated declaration either fails to link or fails
during loader symbol resolution.

As a header-level example, JACK marks APIs such as
jack_set_latency_callback with JACK_WEAK_EXPORT so a caller can check
whether the function exists when using an older JACK library. Bindgen emits
that declaration as an ordinary extern function too.

--dynamic-loading WeakApi is a separate opt-in libloading workaround.
Without --dynamic-link-require-all, both api_version and optional_api
become fallible lookups. It does not preserve the default linker behavior or
infer optionality from the weak attribute.

$ bindgen --version
bindgen 0.72.1
$ clang --version
Ubuntu clang version 15.0.7
$ rustc --version
rustc 1.75.0

Release target: x86_64-unknown-linux-gnu.

The current-main run used rustc 1.82.0. Its bindgen CLI reported 0.72.0, so the
commit above identifies that build.

Expected Results

A binding that keeps optional_api optional, or an explicit diagnostic if the
selected output mode cannot preserve weak imports.

I am not prescribing a particular Rust representation. Silently emitting an
ordinary extern function changes the weak declaration into a required symbol
on ELF.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions