Skip to content

Unexpected empty bitstring match on non-empty bitstring with specific function clause order #10047

@potatosalad

Description

@potatosalad

Describe the bug
A non-empty 8-bit bitstring (like <<0>>) is matched as an empty bitstring (<<>>) when specific function clause ordering is used.

This was discovered when running property based testing on argo_varbit.

To Reproduce

Using the following module:

-module(varbit).

-export([
    write/1
]).

-spec write(undefined | bitstring()) -> binary().
write(undefined) ->
    <<>>;
write(Varbit) when is_bitstring(Varbit) ->
    write_internal(Varbit).

-spec write_internal(bitstring()) -> binary().
write_internal(<<Chunk:7/bits>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 15
    <<Chunk:7/bits, 0:1>>;
write_internal(<<Chunk:6/bits>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 18
    <<Chunk:6/bits, 0:2>>;
write_internal(<<Chunk:5/bits>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 21
    <<Chunk:5/bits, 0:3>>;
write_internal(<<Chunk:4/bits>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 24
    <<Chunk:4/bits, 0:4>>;
write_internal(<<Chunk:3/bits>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 27
    <<Chunk:3/bits, 0:5>>;
write_internal(<<Chunk:2/bits>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 30
    <<Chunk:2/bits, 0:6>>;
write_internal(<<Chunk:1/bits>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 33
    <<Chunk:1/bits, 0:7>>;
write_internal(<<>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 36
    <<0:8>>;
write_internal(<<Chunk:7/bits, Rest/bits>>) ->
    io:format("~ts:~w\n", [?FUNCTION_NAME, ?LINE]), % ?LINE = 39
    <<Chunk:7/bits, 1:1, (write_internal(Rest))/bits>>.

Using an Erlang shell:

1> c(varbit).
{ok,varbit}
2> varbit:write(<<1>>).
write_internal:36
<<0>>

Expected behavior

2> varbit:write(<<0>>).
write_internal:39
write_internal:15
<<1,0>>

Affected versions
OTP 27, 28+

Additional context
Observations:

  1. Adding -export([write_internal/1]). causes the unexpected behavior to go away.
  2. Changing the function clause ordering where write_internal(<<>>) appears after write_internal(<<Chunk:7/bits, Rest/bits>>) causes the unexpected behavior to go away.

Metadata

Metadata

Assignees

Labels

bugIssue is reported as a bugteam:VMAssigned to OTP team VM

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions