Stream: bugs

Topic: Panic use-after-free with long Stdin + while loop


view this post on Zulip Matthieu Pizenberg (Dec 12 2025 at 10:43):

I’ve got another panic with long stdin input. Using the same oneline.txt stdin as in #bugs > ✔ Panic use-after-free with long Stdin + List.append

This time, the minimal bug happens with a while loop.

main! = |_args| {
    while True {
        if Stdin.line!() == "" {
            return Ok({})
        }
    }
    Ok({})
}

I opened an issue https://github.com/roc-lang/roc/issues/8642

view this post on Zulip Richard Feldman (Dec 12 2025 at 14:18):

https://github.com/roc-lang/roc/pull/8649 should fix this

view this post on Zulip Astating (Dec 12 2025 at 15:06):

Richard Feldman said:

https://github.com/roc-lang/roc/pull/8649 should fix this

Hello, I've tested some code with a similar error on your fix branch, and I still got the following use-after-free error :

The spaghetti-flavored, not minimal repro-ified code below :

the_menagerie_with_cumulative_parts =
    ["fly", "spider", "bird", "cat", "dog", "goat", "cow", "horse"]
        .fold(
            {list: [], acc_str: ""},
            |state, animal| {
                dbg state
                if animal == "horse" {
                    return {
                        list: state.list.append(
                            (animal, ""),
                        ),
                        acc_str: "",
                    }
                }


                new_part = [add_line(animal), state.acc_str]->Str.join_with("\n")

                {
                    list: state.list.append(
                        (animal, new_part),
                    ),
                    acc_str: new_part,
                }
            },
        )

add_line = |animal|
    match animal {
        "fly" =>
            \\I don't know why she swallowed a fly.
            \\Perhaps she'll die.

        "spider" =>
            "She swallowed the spider to catch the fly."

        "bird" =>
            "She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her."

        "cat" =>
            "She swallowed the cat to catch the bird."

        "dog" =>
            "She swallowed the dog to catch the cat."

        "goat" =>
            "She swallowed the goat to catch the dog."
        }

The check output :

[dbg] { acc_str: "", list: [] }
[dbg] { acc_str: "I don't know why she swallowed a fly.
Perhaps she'll die.
", list: [("fly", "I don't know why she swallowed a fly.
Perhaps she'll die.
")] }
thread 68798 panic: Use-after-free: decref on already-freed memory
/home/astating/roc/src/builtins/utils.zig:525:13: 0x4ffea36 in decref (mod.zig)
            @panic("Use-after-free: decref on already-freed memory");
            ^
/home/astating/roc/src/builtins/str.zig:267:40: 0x4ce0606 in decref (mod.zig)
            @import("utils.zig").decref(self.getAllocationPtr(), self.capacity_or_alloc_ptr, RocStr.alignment, false, roc_ops);
                                       ^
/home/astating/roc/src/eval/StackValue.zig:114:23: 0x4ce0c34 in decrefLayoutPtr (mod.zig)
        roc_str.decref(ops);
                      ^
/home/astating/roc/src/eval/StackValue.zig:210:28: 0x4ce21b0 in decrefLayoutPtr (mod.zig)
            decrefLayoutPtr(elem_layout, elem_ptr, layout_cache, ops);
                           ^
/home/astating/roc/src/eval/StackValue.zig:133:36: 0x4ce11f9 in decrefLayoutPtr (mod.zig)
                    decrefLayoutPtr(elem_layout, @ptrCast(elem_ptr), layout_cache, ops);
                                   ^
/home/astating/roc/src/eval/StackValue.zig:192:28: 0x4ce1d07 in decrefLayoutPtr (mod.zig)
            decrefLayoutPtr(field_layout, field_ptr, layout_cache, ops);
                           ^
/home/astating/roc/src/eval/StackValue.zig:1730:28: 0x4a38b40 in decref (mod.zig)
            decrefLayoutPtr(self.layout, self.ptr, layout_cache, ops);
                           ^
/home/astating/roc/src/eval/interpreter.zig:15802:60: 0x4da4664 in applyContinuation (mod.zig)
                        self.bindings.items[j].value.decref(&self.runtime_layout_store, roc_ops);
                                                           ^
/home/astating/roc/src/eval/interpreter.zig:9852:71: 0x4a72680 in evalWithExpectedType (mod.zig)
                    const should_continue = try self.applyContinuation(&work_stack, &value_stack, cont, roc_ops);
                                                                      ^
/home/astating/roc/src/eval/interpreter.zig:558:45: 0x4a379cf in eval (mod.zig)
        return try self.evalWithExpectedType(expr_idx, roc_ops, null);
                                            ^
/home/astating/roc/src/eval/comptime_evaluator.zig:291:45: 0x47fd32b in evalDecl (mod.zig)
        const result = self.interpreter.eval(expr_idx, ops) catch |err| {
                                            ^
/home/astating/roc/src/eval/comptime_evaluator.zig:1494:50: 0x47ff101 in evalAll (mod.zig)
                const eval_result = self.evalDecl(def_idx) catch |err| {
                                                 ^
/home/astating/roc/src/compile/compile_package.zig:1065:43: 0x49c0984 in typeCheckModule (mod.zig)
        _ = try comptime_evaluator.evalAll();
                                          ^
/home/astating/roc/src/compile/compile_package.zig:1118:42: 0x49c1c38 in doTypeCheck (mod.zig)
        var checker = try typeCheckModule(self.gpa, env, self.builtin_modules.builtin_module.env, imported_envs.items);
                                         ^
/home/astating/roc/src/compile/compile_package.zig:593:37: 0x49c48fc in process (mod.zig)
                try self.doTypeCheck(task.module_id);
                                    ^
/home/astating/roc/src/compile/compile_package.zig:365:33: 0x49c59d2 in runSingleThread (mod.zig)
                try self.process(task);
                                ^
/home/astating/roc/src/compile/compile_package.zig:349:65: 0x49c7846 in buildRoot (mod.zig)
                    .single_threaded => try self.runSingleThread(),
                                                                ^
/home/astating/roc/src/compile/compile_build.zig:569:35: 0x49ce37d in build (mod.zig)
        try root_sched.*.buildRoot(pkg_root_file);
                                  ^
/home/astating/roc/src/cli/main.zig:4411:20: 0x49d3e12 in checkFileWithBuildEnv (main.zig)
    build_env.build(filepath) catch {
                   ^
/home/astating/roc/src/cli/main.zig:4508:45: 0x45e5e6e in rocCheck (main.zig)
    var check_result = checkFileWithBuildEnv(
                                            ^
/home/astating/roc/src/cli/main.zig:741:40: 0x486ff28 in mainArgs (main.zig)
        .check => |check_args| rocCheck(allocs, check_args),
                                       ^
/home/astating/roc/src/cli/main.zig:678:13: 0x48724c8 in main (main.zig)
    mainArgs(&allocs, args) catch {
            ^
/home/astating/zig-x86_64-linux-0.15.2/lib/std/start.zig:627:37: 0x4872a81 in main (std.zig)
            const result = root.main() catch |err| {
                                    ^
/home/astating/zig-x86_64-linux-0.15.2/lib/libc/musl/src/env/__libc_start_main.c:95:7: 0x9621e9f in libc_start_main_stage2 (/home/astating/zig-x86_64-linux-0.15.2/lib/libc/musl/src/env/__libc_start_main.c)
 exit(main(argc, argv, envp));
      ^
???:?:?: 0x957af09 in ??? (???)
Unwind error at address `exe:0x957af09` (error.MissingFDE), trace may be incomplete

Aborted

run output :

dbg: { acc_str: "", list: [] }

Roc crashed: e_closure(expr=125): failed to resolve capture 'the_menagerie_with_cumulative_parts' (pattern_idx=0) in module 'food_chain.roc', bindings.len=9

Should I create a separate issue ?

view this post on Zulip Anton (Dec 12 2025 at 15:08):

Hi @Astating,
Can you try again with roc yourfile.roc --no-cache to make sure it is using the latest stuff?

view this post on Zulip Astating (Dec 12 2025 at 15:10):

Anton said:

Hi Astating,
Can you try again with roc yourfile.roc --no-cache to make sure it is using the latest stuff?

Done, same outputs.

With a little bit reduced example with smaller strings, it does not fail on the same iteration but a bit later :

the_menagerie_with_cumulative_parts =
    ["fly", "spider", "bird", "cat", "dog", "goat", "cow", "horse"]
        .fold(
            {list: [], acc_str: ""},
            |state, animal| {
                dbg state

                new_part = [animal, state.acc_str]->Str.join_with("\n")

                {
                    list: state.list.append(
                        (animal, new_part),
                    ),
                    acc_str: new_part,
                }
            },
        )
[dbg] { acc_str: "", list: [] }
[dbg] { acc_str: "fly
", list: [("fly", "fly
")] }
[dbg] { acc_str: "spider
fly
", list: [("fly", "fly
"), ("spider", "spider
fly
")] }
[dbg] { acc_str: "bird
spider
fly
", list: [("fly", "fly
"), ("spider", "spider
fly
"), ("bird", "bird
spider
fly
")] }
[dbg] { acc_str: "cat
bird
spider
fly
", list: [("fly", "fly
"), ("spider", "spider
fly
"), ("bird", "bird
spider
fly
"), ("cat", "cat
bird
spider
fly
")] }
[dbg] { acc_str: "dog
cat
bird
spider
fly
", list: [("fly", "fly
"), ("spider", "spider
fly
"), ("bird", "bird
spider
fly
"), ("cat", "cat
bird
spider
fly
"), ("dog", "dog
cat
bird
spider
fly
")] }
thread 70700 panic: Use-after-free: decref on already-freed memory

view this post on Zulip Anton (Dec 12 2025 at 15:32):

Should I create a separate issue ?

Yes, please


Last updated: Dec 21 2025 at 12:15 UTC