Skip to content

Can not set the bitstring value in a struct definition #14362

@Eiji7

Description

@Eiji7

Elixir and Erlang/OTP versions

$ elixir --version
Erlang/OTP 27 [erts-15.2.3] [source] [64-bit] [smp:32:32] [ds:32:32:10] [async-threads:1] [jit:ns]

Elixir 1.18.3 (compiled with Erlang/OTP 27)

Operating system

Gentoo Linux with default/linux/amd64/23.0/desktop/plasma profile and 6.12.16 kernel version.

Current behavior

Defining a struct with default bitstring value raises ArgumentError with message argument error. It happens only if the size is not a multiple of 8.

The following examples are failing:

defmodule Example1, do: defstruct value: <<0::1>>
defmodule Example2, do: defstruct value: <<0::2>>
defmodule Example3, do: defstruct value: <<0::3>>
defmodule Example4, do: defstruct value: <<0::4>>
defmodule Example5, do: defstruct value: <<0::5>>
defmodule Example6, do: defstruct value: <<0::6>>
defmodule Example7, do: defstruct value: <<0::7>>

defmodule Example9, do: defstruct value: <<0::9>>
defmodule Example10, do: defstruct value: <<0::10>>
defmodule Example11, do: defstruct value: <<0::11>>
defmodule Example12, do: defstruct value: <<0::12>>
defmodule Example13, do: defstruct value: <<0::13>>
defmodule Example14, do: defstruct value: <<0::14>>
defmodule Example15, do: defstruct value: <<0::15>>

The following examples are working:

defmodule Example8, do: defstruct value: <<0::8>>
defmodule Example16, do: defstruct value: <<0::16>>
defmodule Example24, do: defstruct value: <<0::24>>
defmodule Example32, do: defstruct value: <<0::32>>
defmodule Example40, do: defstruct value: <<0::40>>
defmodule Example48, do: defstruct value: <<0::48>>
defmodule Example56, do: defstruct value: <<0::56>>

Here is a complete error with stacktrace for Example1:

** (ArgumentError) argument error
    (elixir 1.18.3) src/elixir_erl.erl:109: :elixir_erl.elixir_to_erl(<<0::size(1)>>, 0)
    (elixir 1.18.3) src/elixir_erl.erl:70: :elixir_erl."-elixir_to_erl/2-lc$^1/1-0-"/2
    (elixir 1.18.3) src/elixir_erl.erl:71: :elixir_erl.elixir_to_erl/2
    (elixir 1.18.3) src/elixir_erl.erl:111: :elixir_erl.elixir_to_erl_cons/2
    (elixir 1.18.3) src/elixir_erl.erl:339: :elixir_erl.struct_info/1
    (elixir 1.18.3) src/elixir_erl.erl:311: :elixir_erl.add_info_function/6
    (elixir 1.18.3) src/elixir_erl.erl:286: :elixir_erl.functions_form/8
    iex:15: (file)

Expected behavior

I don't see why bitstring as a default value may be not supported especially that it works in version 1.17.3.

What's interesting is that the below call also does not work:

iex> :elixir_erl.elixir_to_erl(<<0::size(1)>>, 0)
** (ArgumentError) argument error
    (elixir 1.17.3) src/elixir_erl.erl:96: :elixir_erl.elixir_to_erl/2
    iex:1: (file)

which means that either :elixir_erl.elixir_to_erl/2 does not support non UTF-8 bitstrings or if it's expected then this call is incorrectly used since Elixir version 1.18.0-rc.0.


I've tried to search for a root issue. I believe I found a commit that introduces this bug: cb2e036. To be even more precise it happened in the first commit of #13984 which is: 792e604

  1. Looks like a module paraller checker is now calling a :elixir_erl.debug_info/4:
    module_tuple =
    cond do
    is_tuple(info) ->
    info
    is_binary(info) ->
    with {:ok, binary} <- File.read(info),
    {:ok, {_, [debug_info: chunk]}} <- :beam_lib.chunks(binary, [:debug_info]),
    {:debug_info_v1, backend, data} = chunk,
    {:ok, module_map} <- backend.debug_info(:elixir_v1, module, data, []) do
    cache_from_module_map(table, module_map)
    else
    _ -> nil
    end
    end
  2. which calls :elixir_erl.dynamic_form/1:
    %% debug_info callback
    debug_info(elixir_v1, _Module, none, _Opts) ->
    {error, missing};
    debug_info(elixir_v1, _Module, {elixir_v1, Map, _Specs}, _Opts) ->
    {ok, Map};
    debug_info(erlang_v1, _Module, {elixir_v1, Map, Specs}, _Opts) ->
    {Prefix, Forms, _, _, _} = dynamic_form(Map),
    {ok, Prefix ++ Specs ++ Forms};
    debug_info(core_v1, _Module, {elixir_v1, Map, Specs}, Opts) ->
    {Prefix, Forms, _, _, _} = dynamic_form(Map),
    #{compile_opts := CompileOpts} = Map,
    AllOpts = CompileOpts ++ Opts,
    %% Do not rely on elixir_erl_compiler because we don't warn
    %% warnings nor the other functionality provided there.
    case elixir_erl_compiler:erl_to_core(Prefix ++ Specs ++ Forms, AllOpts) of
    {ok, CoreForms, _} ->
    try compile:noenv_forms(CoreForms, [no_spawn_compiler_process, from_core, to_core, return | AllOpts]) of
    {ok, _, Core, _} -> {ok, Core};
    _What -> {error, failed_conversion}
    catch
    error:_ -> {error, failed_conversion}
    end;
    _ ->
    {error, failed_conversion}
    end;
    debug_info(_, _, _, _) ->
    {error, unknown_format}.
  3. which calls :elixir_erl.functions_form/8:
    dynamic_form(#{module := Module, relative_file := RelativeFile,
    attributes := Attributes, definitions := Definitions, unreachable := Unreachable,
    deprecated := Deprecated, compile_opts := Opts} = Map) ->
    %% TODO: Match on anno directly in Elixir v1.22+
    Line = case Map of
    #{anno := AnnoValue} -> erl_anno:line(AnnoValue);
    #{line := LineValue} -> LineValue
    end,
    {Def, Defmacro, Macros, Exports, Functions} =
    split_definition(Definitions, Unreachable, Line, [], [], [], [], {[], []}),
    FilteredOpts = lists:filter(fun({no_warn_undefined, _}) -> false; (_) -> true end, Opts),
    Location = {elixir_utils:characters_to_list(RelativeFile), Line},
    Prefix = [{attribute, Line, file, Location},
    {attribute, Line, module, Module},
    {attribute, Line, compile, [no_auto_import | FilteredOpts]}],
    Struct = maps:get(struct, Map, nil),
    Forms0 = functions_form(Line, Module, Def, Defmacro, Exports, Functions, Deprecated, Struct),
    Forms1 = attributes_form(Line, Attributes, Forms0),
    {Prefix, Forms1, Def, Defmacro, Macros}.
  4. (rest is in the stacktrace above)

If I'm correct the changes in type system caused a bug in struct definition. Since we want to keep a backwards compatibility we have to add bitstring support for the :elixir_erl.elixir_to_erl/2 function.

Related forum topic:
https://elixirforum.com/t/can-not-set-bitstring-value-in-defstruct-in-elixir-1-18-is-this-a-bug/70121?u=eiji

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions