Working on the JWT impl for basic-webserver, and I have a segfault I'm trying to track down.
@Brendan Hansknecht I've isolated it to the call to the following... does this look right? (I know there is a lot of context missing) .. specifically, I suspect the key
here is being dropped at the end of this function and that is messing with the heap somehow. Is the data copied into the heap or just a reference?
#[roc_fn(name = "jwtDecodingKeyFromRsaPem")]
pub extern "C" fn jwtDecodingKeyFromRsaPem(
secret: &RocStr,
) -> RocResult<RocBox<()>, glue_internal_jwt::JwtErr> {
dbg!(&secret);
let key = match jsonwebtoken::DecodingKey::from_rsa_pem(secret.as_bytes()) {
Ok(key) => key,
Err(err) => return RocResult::err(dbg!(jwt_err_to_roc(err))),
};
dbg!("IN HERE");
let heap = jwt_key_heap();
let alloc_result = heap.alloc_for(key);
match alloc_result {
Ok(out) => RocResult::ok(out),
Err(_) => RocResult::err(glue_internal_jwt::JwtErr::Other(
"Ran out of memory allocating space for DecodingKey".into(),
)),
}
}
I'm wondering if I need to tell rust not to drop this value somehow.
This topic was moved here from #compiler development > bug possibly from using threadsafe refcounted heap by Luke Boswell.
I've tried switching to use a ManuallyDrop, but that hasn't helped.
static JWT_KEY_HEAP: OnceLock<ThreadSafeRefcountedResourceHeap<ManuallyDrop<jsonwebtoken::DecodingKey>>>
Link to PR https://github.com/roc-lang/basic-webserver/pull/72 in case that helps
The heap takes ownership of the data passed in so it definitely shouldn't get dropped. Even if it was dropped, it shouldn't segfault until attempted use at some other point in the app
My first guess would be that something is wrong with the return result type maybe the error tag specifically. But that is just a wild guess from what commonly goes wrong and can lead to segfaults
Something fishy is going on... I change the type to jwtDecodingKeyFromRsaPem(secret: &RocStr) -> RocResult<RocBox<()>, ()>
and now roc is seeing an err
, even though it's returning an ok
That's not surprising
That would move the tag id location
Also changed on the roc side
jwtDecodingKeyFromRsaPem : Str -> Task InternalJwt.DecodingKey {}
Oh....hmmm :thinking:
And the decoding key is just Box {}
, right?
Oh wait... I think I had InternalJwt.DecodingKey := Box {}
Maybe that should be a :
instead of :=
I don't think it should make a difference here, but worth trying
Yeah, hasn't changed anything
I did simply the type so across the host boundary it's now just jwtDecodingKeyFromRsaPem : Str -> Task (Box {}) {}
I can take a look tomorrow. No immediate ideas on a quick skim of the PR. Does the llvm ir type look normal?
Also, can you tell if it is segfaulting in rust or in roc?
Valgrind may also help here
declare { [0 x i64], [1 x i64], i8, [7 x i8] } @roc_fx_jwtDecodingKeyFromRsaPem(ptr)
define internal fastcc void @roc_fx_jwtDecodingKeyFromRsaPem_fastcc_wrapper(ptr %0, ptr %1) {
entry:
%tmp = call { [0 x i64], [1 x i64], i8, [7 x i8] } @roc_fx_jwtDecodingKeyFromRsaPem(ptr %0), !dbg !330
store { [0 x i64], [1 x i64], i8, [7 x i8] } %tmp, ptr %1, align 8, !dbg !330
ret void, !dbg !330
}
define internal fastcc void @PlatformTasks_jwtDecodingKeyFromRsaPem_48c2caee6f1010356bbec8845a6ee45f2928c63eece16acf25dc3f84dc5f6(ptr %closure_arg_jwtDecodingKeyFromRsaPem_0, ptr %0) !dbg !272 {
entry:
tail call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(24) %0, ptr noundef nonnull align 8 dereferenceable(24) %closure_arg_jwtDecodingKeyFromRsaPem_0, i64 24, i1 false), !dbg !273
ret void, !dbg !273
}
define internal fastcc void @PlatformTasks_task_closure_jwtDecodingKeyFromRsaPem_f752fd971dee73f4bef39e126f15a0a84437112755ca589db8702463ce739a({} %"130", ptr %closure_arg_jwtDecodingKeyFromRsaPem_0, ptr %0) !dbg !329 {
entry:
%result_value = alloca { [0 x i64], [1 x i64], i8, [7 x i8] }, align 8
call fastcc void @roc_fx_jwtDecodingKeyFromRsaPem_fastcc_wrapper(ptr %closure_arg_jwtDecodingKeyFromRsaPem_0, ptr nonnull %result_value), !dbg !330
call fastcc void @"#Attr_#dec_2"(ptr %closure_arg_jwtDecodingKeyFromRsaPem_0), !dbg !330
call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(16) %0, ptr noundef nonnull align 8 dereferenceable(16) %result_value, i64 16, i1 false), !dbg !330
ret void, !dbg !330
}
This is the new Box {}
version with error type of {}
?
Brendan Hansknecht said:
Valgrind may also help here
Ah, good idea... I should have thought of that. I'm just on my mac trying random things. I'll switch over to the linux machine
Yeah, llvm ir type looks good
I'm relieved to hear you say that... but also now more confused
Here is the full valgrind output
And a snippet.
==118423== Conditional jump or move depends on uninitialised value(s)
==118423== at 0x1AE641: roc_fx_jwtDecodingKeyFromRsaPem (in /home/lb-dev/github/basic-webserver/examples/json-web-token)
==118423== by 0x12A873: roc__forHost_0_caller (in /home/lb-dev/github/basic-webserver/examples/json-web-token)
==118423== by 0x175C01: rust_main (in /home/lb-dev/github/basic-webserver/examples/json-web-token)
==118423== by 0x488110D: (below main) (in /nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5/lib/libc.so.6)
According to GPT-4o :smiley:
Uninitialized Memory in RocStr
:
Ensure that the RocStr
passed to the function is properly initialized. If RocStr
is a custom type, make sure its internal data is correctly set up before being used.
Uninitialized Memory in jwt_key_heap
:
The function jwt_key_heap()
is called to get a heap allocator. Ensure that this allocator is properly initialized and that it doesn't return uninitialized memory.
Uninitialized Memory in alloc_for
:
The alloc_for
method is called on the heap allocator. Ensure that this method does not return uninitialized memory or depend on uninitialized values.
@Brendan Hansknecht if you could have a look tomorrow that would be super.
I've stripped it back to a minimal repro (commented everything else out)... and used only the most simple types RocStr
RocBox<()>
etc. I've double checked the flow and types and it all looks good to me... so I'm scratching my head a little confused how rust is returning an RocResult::ok
but roc is getting an Task.err {}
.
I'm going to take a break and pick this back up again tomorrow. I've pushed the latest I have for debugging to that branch.
Took a bit of a look....not seeing the issue currently.
One guess for why some of these things work in basic-cli but no here is that either roc_std
or heap.rs
have different versions and there is a bug.
Ohk, that makes
Gives me some ideas to test out
I tried using roc_std from roc-lang/roc, that hasn't helped. :sad:
Last updated: Jul 06 2025 at 12:14 UTC