Stream: beginners

Topic: Segmentation fault during exercism tests for phone-number


view this post on Zulip Tom Hill (Apr 30 2025 at 20:29):

How can I investigate the cause of a segmentation fault?

I've encountered a segmentation fault when running tests for the phone-number exercise in attempting to debug my solution.

Tests code

app [main!] {
    pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.19.0/Hj-J_zxz7V9YurCSTFcFdu6cQJie4guzsPMUi5kBYUk.tar.br",
}

import pf.Stdout

main! = |_args|
    Stdout.line!("")

import PhoneNumber exposing [clean]

# invalid when 11 digits does not start with a 1
expect
    result = clean("22234567890")
    result |> Result.is_err

# valid when 11 digits and starting with 1
expect
    result = clean("12234567890")
    expected = Ok("2234567890")
    result == expected

PhoneNumber.roc

module [clean]

clean : Str -> Result Str _
clean = |phone_number|
    parsed =
        Str.to_utf8(phone_number)
        |> List.keep_if(|c| c > '0' and c <= '9')

    when parsed is
        ['1', a, _, _, b, .. as rest] if a > '1' and b > '1' and List.len(rest) == 6 -> Str.from_utf8(List.drop_first(parsed, 1))
        [a, _, _, b, .. as rest] if a > '1' and b > '1' and List.len(rest) == 6 -> Str.from_utf8(parsed)
        _ -> Err(InvalidFormat)

Interestingly if run the tests individually then no segmentation fault occurs. Also no segmentation fault if I alter the implementation (this corrects the logic so that all tests pass), by changing a GT to a GTE in the line:

List.keep_if(|c| c > '0' and c <= '9')

to

List.keep_if(|c| >= '0' and c <= '9')

I'm using:

roc nightly pre-release, built from commit c47a8e9cdac on Sat Mar 22 09:14:20 UTC 2025

view this post on Zulip Brendan Hansknecht (Apr 30 2025 at 23:36):

Is the compiler segfaulting or the executable?

view this post on Zulip Brendan Hansknecht (Apr 30 2025 at 23:37):

Oh, it's expects specifically

view this post on Zulip Brendan Hansknecht (Apr 30 2025 at 23:37):

Probably the communication better compiler and test runner

view this post on Zulip Brendan Hansknecht (Apr 30 2025 at 23:37):

Could try running with old or gdb

view this post on Zulip Anton (May 02 2025 at 09:02):

I was able to reproduce this and am checking it out with valgrind now

view this post on Zulip Anton (May 02 2025 at 09:06):

I switched the expects around and got some memory garbage in the output:

── EXPECT FAILED in temp.roc ───────────────────────────────────────────────────

This expectation failed:

12│>  # valid when 11 digits and starting with 1
13│>  expect
14│>      result = clean("12234567890")
15│>      expected = Ok("2234567890")
16│>      result == expected

When it failed, these variables had these values:

result : Result Str [
    BadUtf8 {
        index : U64,
        problem : Utf8Problem,
    },
    InvalidFormat,
]
result = Ok
    """
    ����Г
    Eʪʪʪʲ234567890
    """

expected : [
    Err [
        BadUtf8 {
            index : U64,
            problem : Utf8Problem,
        },
        InvalidFormat,
    ],
    Ok Str,
]
expected = Ok "2234567890"

view this post on Zulip Anton (May 02 2025 at 09:34):

This is likely very similar to #bugs > ✔ segfault on failing multi-line tests with a float def @ 💬

view this post on Zulip Anton (May 02 2025 at 11:42):

Hmm, the important valgrind error (at the end) is different from the one in the other thread.
valgrind_output_expect_phone_number_exercism.txt

view this post on Zulip Anton (May 02 2025 at 13:29):

This assertion is hit when building in debug mode: https://github.com/roc-lang/roc/blob/fac7041d11a70751f271c90dbf8354e746c18837/crates/repl_expect/src/app.rs#L25

view this post on Zulip Anton (May 02 2025 at 13:35):

Anyway, this does come down to #bugs > ✔ segfault on failing multi-line tests with a float def @ 💬 . If the new compiler takes too long we will fix this in the old compiler.

view this post on Zulip Anton (May 02 2025 at 13:49):

@Tom Hill with the next release you will get a nicer error message, see PR#7773.

view this post on Zulip Anton (May 02 2025 at 13:57):

As a workaround you can write your own expect function:

my_expect! : Str, Result Str _ => Result {} _
my_expect! = |input, expected|
    result = clean(input)
    if result == expected then
        Stdout.line!("Test passed for input: ${input}")
    else
        Stdout.line!("Test failed for input: ${input}. Expected: ${Inspect.to_str(expected)}, but got: ${Inspect.to_str(result)}")

view this post on Zulip Tom Hill (May 02 2025 at 21:07):

Thank you for investigating :grinning:

view this post on Zulip Anton (May 19 2025 at 12:12):

I've made a general issue for this problem #7799


Last updated: Jul 05 2025 at 12:14 UTC