rust-lang / rust-lang/rust

FFI + release mode + passing pointer to temporary value to C function produces an invalid result

Open
#136,676 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-gub
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Playing around with SDL3 crate I've met behavior which is interestingly even caught in C++: foo(&bar()) where bar returns struct in C gives error like "Taking the address of a temporary object of type 'T'". When rust calls C function, it allows similar code and bad things might happen in release mode:

The code (optionally) creates C struct and calls C func passing a pointer to it.

use std::ffi::c_float;
#[repr(C)]
struct DataFFI { pub a: c_float, pub b: c_float }
fn make_data(x:f32) -> DataFFI { DataFFI { a: x, b: x } }
extern "C" { fn sum_me(dat: *const DataFFI) -> c_float; }
fn do_sum(dat: Option<f32>) -> f32
{
    unsafe {
        sum_me(
            match dat { Some(x) => &make_data(x), None => std::ptr::null() },
        )
    }
}

fn main()
{
    println!("expect: 0, got: {}", do_sum(None));
    println!("expect: 8, got: {}", do_sum(Some(4.0)));
}

build.rs is done like this

fn main()
{
    println!("cargo::rustc-flags=-L/home/fella/src/memtest/c");
    println!("cargo::rustc-flags=-lbackend");
}   

C function is

//gcc -shared -o libbackend.so -fPIC backend.c , compiled inside `c` folder
#include <stdio.h>
typedef struct Data { float a,b; } Data;
const Data nul_data={0.0};
float sum_me(const Data* dat)
{
    float res=0.0;
    printf("DAT(%p):\n", dat);
    dat = dat ? dat : &nul_data;
    res += dat->a; printf("%f ", res); res += dat->b; printf("%f\n", res);
    return res;
}

I expected to see this happen: explanation

2  ╰ LD_LIBRARY_PATH=/home/fella/src/memtest/c/ cargo run 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/memtest`
DAT((nil)):
0.000000 0.000000
expect: 0, got: 0
DAT(0x7ffef9cfdb3c):
4.000000 8.000000
expect: 8, got: 8

And this happens in debug mode: when dat is passed it contains what we want. But in release mode

Instead, this happened:

✦2  ╰ LD_LIBRARY_PATH=/home/fella/src/memtest/c/ cargo run --release
    Finished `release` profile [optimized] target(s) in 0.00s
     Running `target/release/memtest`
DAT((nil)):
0.000000 0.000000
expect: 0, got: 0
DAT(0x7fff5f15e0c0):
-1833370141065216.000000 -1833370141065216.000000
expect: 8, got: -1833370100000000

(Also interestingly in this run we have -1833370100000000 without 41065216)

It relies on both using address from return value and match. If we use

fn do_sum(dat: Option<f32>) -> f32
{
    unsafe {
        let real_dat;
        sum_me(
            match dat { Some(x) => {
                real_dat = make_data(x);
                &real_dat
            },
            None => std::ptr::null() },
        )
    }
}

or sum_dat(&make_data()) it works.

Meta

rustc --version --verbose:

rustc 1.84.1 (e71f9a9a9 2025-01-27)
binary: rustc
commit-hash: e71f9a9a98b0faf423844bf0ba7438f29dc27d58
commit-date: 2025-01-27
host: x86_64-unknown-linux-gnu
release: 1.84.1
LLVM version: 19.1.5

Backtrace

<backtrace>

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the provided Rust and C reproducer, running both cargo run and cargo run --release with rustc 1.84.1. Compare the temporary-value pointer behavior with the shown local-variable workaround, then determine whether the result matches Rust's temporary lifetime and FFI rules. Done means the behavior is explained and, if confirmed as a compiler bug, covered by an appropriate regression test.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.