Stream: compiler development

Topic: valgrind: conditional jump depends on uninitialized values


view this post on Zulip Richard Feldman (Jul 25 2023 at 18:26):

I feel like I've seen this before in valgrind:

==260== Conditional jump or move depends on uninitialised value(s)
==260==  at 0x1A082C: _16_d394208415ac8fe0ce8aa0ddf6a845c7cc74d818698e3d25c85705ce311f5ec (in /usr/src/app)
==260==  by 0x1A235F: roc__mainForHost_0_caller (in /usr/src/app)
==260==  by 0x7AC92F: roc_app::RocFunction_89::force_thunk (lib.rs:44)

anyone have any info on why it happens, or what it's referring to?

view this post on Zulip Ayaz Hafiz (Jul 25 2023 at 19:55):

it means you're probably comparing against a memory cell that wasn't initialized or set to nullptr

view this post on Zulip Ayaz Hafiz (Jul 25 2023 at 19:56):

so , probably some value in the condition at roc_app::RocFunction_89::force_thunk (lib.rs:44) is not intialized or zeroed out

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:10):

hm, here's the code in question:

impl RocFunction_89 {
    pub fn force_thunk(self) -> roc_std::RocStr {
        extern "C" {
            fn roc__mainForHost_0_caller(
                arg0: *const (),
                closure_data: *mut u8,
                output: *mut roc_std::RocStr,
            );
        }

        let mut output = core::mem::MaybeUninit::uninit();
        let closure_ptr =
            (&mut core::mem::ManuallyDrop::new(self.closure_data)) as *mut _ as *mut u8;

        unsafe {
            roc__mainForHost_0_caller(&(), closure_ptr, output.as_mut_ptr());

            output.assume_init()
        }
    }
}

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:10):

line 44 is this one:

roc__mainForHost_0_caller(&(), closure_ptr, output.as_mut_ptr());

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:11):

output is supposed to be uninitalized since it's being written to (so, nothing in the generated code should be looking at it, just writing to it)

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:11):

the first argument is a pointer to nothing, so definitely nothing should be reading from that under any circumstances :thinking:

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:12):

I guess it's possible the closure data hasn't been initialized, since that comes from a call to roc__mainForHost_1_exposed_generic

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:12):

hm, I'll try manually initializing each of those and see if the error goes away for one of them

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:20):

Are you running this with optimized roc or non-opt?

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:21):

I think in a number of cases non-opt roc theoretically looks at uninitialized values, but in an impossible way. Like the branch is known false at compile time but we still emit it

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:29):

yeah turns out the closure data is uninitialized

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:29):

if I print its length, it's different on every run, and also valgrind gives additional errors for printing the length :laughing:

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:29):

(reading from uninitialized memory errors, specifically)

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:44):

Do you have closure data that should be loaded here?

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:45):

Is main for host accidentally capturing something?

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:45):

#[repr(C)]
#[derive(Debug, Clone)]
pub struct RocFunction_89 {
    closure_data: roc_std::RocList<u8>,
}

impl RocFunction_89 {
    pub fn force_thunk(self) -> roc_std::RocStr {
        extern "C" {
            fn roc__mainForHost_0_caller(
                arg0: *const (),
                closure_data: *mut u8,
                output: *mut roc_std::RocStr,
            );
        }
        dbg!(self.closure_data.len());

        let mut output = core::mem::MaybeUninit::uninit();
        let closure_ptr =
            (&mut core::mem::ManuallyDrop::new(self.closure_data)) as *mut _ as *mut u8;

        unsafe {
            roc__mainForHost_0_caller(&(), closure_ptr, output.as_mut_ptr());

            output.assume_init()
        }
    }
}

pub fn mainForHost(arg0: roc_std::RocStr) -> RocFunction_89 {
    extern "C" {
        fn roc__mainForHost_1_exposed_generic(
            _: *mut RocFunction_89,
            _: &mut core::mem::ManuallyDrop<roc_std::RocStr>,
        );
    }

    let mut ret = core::mem::MaybeUninit::uninit();

    unsafe {
        roc__mainForHost_1_exposed_generic(
            ret.as_mut_ptr(),
            &mut core::mem::ManuallyDrop::new(arg0),
        );

        ret.assume_init()
    }
}

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:46):

this is from:

platform "webserver"
    requires {} { main : Url -> Task Str [] }
    exposes [ ... ]
    packages {}
    imports [Task.{ Task }, Url.{ Url }]
    provides [mainForHost]

mainForHost : Str -> (Task Str [] as Fx)
mainForHost = \str -> main (Url.fromStr str)

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:47):

so mainForHost returns a Task, which is the RocFunction_89 closure

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:47):

Yeah, so main for host should have no real closure captures.

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:47):

Though it could technically

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:48):

but RocFunction_89 should, right? Because it's a bunch of chained Task.awaits

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:48):

So really our force think function should take in closure capture data from when main for host 1 was called

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:49):

Richard Feldman said:

but RocFunction_89 should, right? Because it's a bunch of chained Task.awaits

Depends on how main is written. This is only about the highest level closure captures, not the nested ones, I think :thinking:

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:54):

Oh, I see, it comes from the RocFunction type. I'm just a bit mixed up...sorry if my comments were off

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 20:55):

So is the issue that you have no closure capture data but are pretending you do? So all accesses to the maybe uninit ret and closure_data are incorrect.

view this post on Zulip Richard Feldman (Jul 25 2023 at 20:58):

this is the LLVM IR

define void @roc__mainForHost_1_exposed_generic({ [0 x i64], [3 x i64], i8, [7 x i8] }* %arg, { i8*, i64, i64 }* %arg1) {
entry:
 %result_value = alloca { [0 x i64], [3 x i64], i8, [7 x i8] }, align 8
 %to_cc_type_ptr = bitcast { i8*, i64, i64 }* %arg1 to %str.RocStr*
 call fastcc void @_mainForHost_f03bf86f79d121cbfd774dec4a65912e99f5f17c33852bbc45e81916e62b53b(%str.RocStr* %to_cc_type_ptr, { [0 x i64], [3 x i64], i8, [7 x i8] }* %result_value)
 %i = bitcast { [0 x i64], [3 x i64], i8, [7 x i8] }* %arg to i8*
 %i2 = bitcast { [0 x i64], [3 x i64], i8, [7 x i8] }* %result_value to i8*
 call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 8 %i, i8* align 8 %i2, i64 ptrtoint ({ [0 x i64], [3 x i64], i8, [7 x i8] }* getelementptr ({ [0 x i64], [3 x i64], i8, [7 x i8] }, { [0 x i64], [3 x i64], i8, [7 x i8] }* null, i32 1) to i64), i1 false)
 ret void
}

view this post on Zulip Richard Feldman (Jul 25 2023 at 21:00):

the type of the first argument (which I expect it to write the return value into) seems...different from what Rust is expecting:

{ [0 x i64], [3 x i64], i8, [7 x i8] }* %arg

view this post on Zulip Richard Feldman (Jul 25 2023 at 21:00):

that's a pointer, but not to a RocList<u8> I don't think :sweat_smile:

view this post on Zulip Richard Feldman (Jul 25 2023 at 21:01):

ignoring the first [0 x i64 ] it's three 8B i64s and then another 8 bytes separated into one i8 and then another [7 x i8]

view this post on Zulip Richard Feldman (Jul 25 2023 at 21:02):

oh - maybe the [3 x i64] is the RocList<u8> and then the i8 is the lambda set ID?

view this post on Zulip Richard Feldman (Jul 25 2023 at 21:02):

and then the [7 x i8] is padding

view this post on Zulip Richard Feldman (Jul 25 2023 at 21:10):

hm, but I guess even if in the closure data struct I specified the extra 8B after the RocList, that still wouldn't explain the undefined reads - because the first 24B are still in the same place regardless :thinking:

view this post on Zulip Brendan Hansknecht (Jul 25 2023 at 23:42):

When did we start putting a roc list there? I thought it was just the bytes directly.

view this post on Zulip Brendan Hansknecht (Jul 26 2023 at 00:04):

that still wouldn't explain the undefined reads

Theory, you are only storing the roc list in RocFunction_89. It should be should be storing the roc list followed by an extra i8.

So you have 2 issues:

  1. the memcpy in the llvm ir you showed is writing past the end of the RocFunction_89 struct into arbitrary stack memory. This is due to writing out a full { [0 x i64], [3 x i64], i8, [7 x i8] }
  2. When you call force_thunk on RocFunction_89, it is only passing in [3 x i64] and is missing the i8. Then roc__mainForHost_0_caller it tries to access the i8 leading to the undefined read.

view this post on Zulip Brendan Hansknecht (Jul 26 2023 at 00:17):

What is the llvm ir for the function signiture of roc__mainForHost_0_caller?


Last updated: Jul 06 2025 at 12:14 UTC