Skip to content

Specific tuples passed to Macro.escape/2 cause it to crash #13593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
kristofka opened this issue May 24, 2024 · 0 comments
Closed

Specific tuples passed to Macro.escape/2 cause it to crash #13593

kristofka opened this issue May 24, 2024 · 0 comments

Comments

@kristofka
Copy link
Contributor

Elixir and Erlang/OTP versions

elixir version 1.16. and up, any otp version.

Operating system

any

Current behavior

Passing a three elements tuple where the first element is :quote, the second is anything but a list, and the third is a list with exactly one or two element causes Macro.escape/2 to crash : minimal reproducible example :

iex(1)> Macro.escape({:quote, :foo, [:bar]})
** (FunctionClauseError) no function clause matching in :lists.keydelete3/3

    The following arguments were given to :lists.keydelete3/3:

        # 1
        :column

        # 2
        1

        # 3
        :foo

    (stdlib 5.2.1) lists.erl:885: :lists.keydelete3/3
    (elixir 1.16.2) src/elixir_quote.erl:561: :elixir_quote.meta/2
    (elixir 1.16.2) src/elixir_quote.erl:294: :elixir_quote.do_quote/3
    iex:1: (file)
iex(1)> Macro.escape({:quote, :foo, [:bar, :baz]})
** (FunctionClauseError) no function clause matching in :lists.keydelete3/3

    The following arguments were given to :lists.keydelete3/3:

        # 1
        :column

        # 2
        1

        # 3
        :foo

    (stdlib 5.2.1) lists.erl:885: :lists.keydelete3/3
    (elixir 1.16.2) src/elixir_quote.erl:561: :elixir_quote.meta/2
    (elixir 1.16.2) src/elixir_quote.erl:294: :elixir_quote.do_quote/3
    iex:1: (file)

This issue was first reported in absinthe : #absinthe-graphql/absinthe/issues/1313

Expected behavior

The function should properly escape the tuple :

iex(1)> Macro.escape({:quote, :foo, [:bar]})
{:{}, [], [:quote, :foo, [:bar]]}

A fix is easy enough and I'll submit a PR shortly. However, I'm not entirely convinced that it is possible to completely fix the issue, as tuples of this form may be ambiguous when using Macro.escape/2. For example we have :

iex(1)> Macro.escape({:quote, [column: 10], [:foo]})
{:{}, [], [:quote, [], [:foo]]}
iex(2)> Macro.escape({:quote, [column: 10], [:foo, :bar]})
{:{}, [], [:quote, [], [:foo, :bar]]}
iex(3)> Macro.escape({:quote, [column: 10], [:foo, :bar, :baz]})
{:{}, [], [:quote, [column: 10], [:foo, :bar, :baz]]}
iex(4)>

Which is surprising and arguably a bug as well, and is not solved by the solution I'll submit.

The issue here is that those are caught by the clauses on line 266 of the src/elixir_quote.erl, namely :

do_quote({quote, Meta, [Arg]}, Q) ->
  TArg = do_quote(Arg, Q#elixir_quote{unquote=false}),

  NewMeta = case Q of
    #elixir_quote{op=add_context, context=Context} -> keystore(context, Meta, Context);
    _ -> Meta
  end,

  {'{}', [], [quote, meta(NewMeta, Q), [TArg]]};

do_quote({quote, Meta, [Opts, Arg]}, Q) ->
  TOpts = do_quote(Opts, Q),
  TArg = do_quote(Arg, Q#elixir_quote{unquote=false}),

  NewMeta = case Q of
    #elixir_quote{op=add_context, context=Context} -> keystore(context, Meta, Context);
    _ -> Meta
  end,

  {'{}', [], [quote, meta(NewMeta, Q), [TOpts, TArg]]};

I have attempted but failed to solve the issue at this point, it's unclear to me what careful pattern matching could solve the issue, if any.

kristofka added a commit to kristofka/elixir that referenced this issue May 24, 2024
Attempts to solve an issue that causes `Macro.escape/2` to crash when
given certain tuples.

Note that, `Macro.escape({:quote, [column: 10], [:foo]})` still produces
: `{:{}, [], [:quote, [], [:foo]]}` which is arguably wrong.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant