Skip to content

Commit 4935188

Browse files
fertapricJosé Valim
authored andcommitted
Error on undefined variables in bitstring segments (#8598)
Binary/bitstring matching allows to dynamically define the `size` of the binary in certain conditions: * if the `size` variable is defined prior to the pattern match: iex> size = 8 iex> <<a::size(size), rest::binary>> = "hello" iex> a 104 * if the `size` variable is matched within the same binary/bitstring match, prior to its use: iex> <<name_size::size(8), name::binary-size(name_size), _rest::binary>> = <<5, "Frank the Walrus">> iex> name "Frank" Other cases are considered illegal patterns, for example: {name_size, <<name::binary-size(name_size), _rest::binary>>} = {5, "Frank the Walrus"} This commit raises a `CompileError: undefined variable ...` for such cases. Signed-off-by: José Valim <[email protected]>
1 parent 9c7edf3 commit 4935188

File tree

3 files changed

+71
-26
lines changed

3 files changed

+71
-26
lines changed

lib/elixir/lib/kernel/special_forms.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,17 @@ defmodule Kernel.SpecialForms do
246246
iex> {name, species}
247247
{"Frank", "Walrus"}
248248
249-
And the variable can be defined in the match itself:
249+
And the variable can be defined in the match itself (prior to its use):
250250
251251
iex> <<name_size::size(8), name::binary-size(name_size), " the ", species::binary>> = <<5, "Frank the Walrus">>
252252
iex> {name, species}
253253
{"Frank", "Walrus"}
254254
255+
However, the size cannot be defined in the match outside the binary/bitstring match:
256+
257+
{name_size, <<name::binary-size(name_size), _rest::binary>>} = {5, <<"Frank the Walrus">>}
258+
** (CompileError): undefined variable "name_size" in bitstring segment
259+
255260
Failing to specify the size for the non-last causes compilation to fail:
256261
257262
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>

lib/elixir/src/elixir_bitstring.erl

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ expand(Meta, Args, E, RequireSize) ->
77
case ?key(E, context) of
88
match ->
99
{EArgs, Alignment, EE} =
10-
expand(Meta, fun elixir_expand:expand/2, Args, [], E, 0, RequireSize),
10+
expand(Meta, fun elixir_expand:expand/2, Args, [], E, E, 0, RequireSize),
1111

1212
case find_match(EArgs) of
1313
false ->
@@ -17,13 +17,13 @@ expand(Meta, Args, E, RequireSize) ->
1717
end;
1818
_ ->
1919
{EArgs, Alignment, {_EC, EV}} =
20-
expand(Meta, fun elixir_expand:expand_arg/2, Args, [], {E, E}, 0, RequireSize),
20+
expand(Meta, fun elixir_expand:expand_arg/2, Args, [], {E, E}, E, 0, RequireSize),
2121
{{'<<>>', [{alignment, Alignment} | Meta], EArgs}, EV}
2222
end.
2323

24-
expand(_BitstrMeta, _Fun, [], Acc, E, Alignment, _RequireSize) ->
24+
expand(_BitstrMeta, _Fun, [], Acc, E, _OriginalE, Alignment, _RequireSize) ->
2525
{lists:reverse(Acc), Alignment, E};
26-
expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, Alignment, RequireSize) ->
26+
expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, OriginalE, Alignment, RequireSize) ->
2727
{ELeft, EL} = expand_expr(Meta, Left, Fun, E),
2828

2929
%% Variables defined outside the binary can be accounted
@@ -38,7 +38,7 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, Alignment, Re
3838
end,
3939

4040
EType = expr_type(ELeft),
41-
{ERight, EAlignment, ES} = expand_specs(EType, Meta, Right, EM, RequireSize or MatchSize),
41+
{ERight, EAlignment, ES} = expand_specs(EType, Meta, Right, EM, OriginalE, RequireSize or MatchSize),
4242

4343
EE =
4444
case EL of
@@ -65,15 +65,15 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, Alignment, Re
6565
prepend_unless_bitstring_in_match(EType, Meta, ELeft, ERight, Acc, E)
6666
end,
6767

68-
expand(BitstrMeta, Fun, T, EAcc, EE, alignment(Alignment, EAlignment), RequireSize);
69-
expand(BitstrMeta, Fun, [{_, Meta, _} = H | T], Acc, E, Alignment, RequireSize) ->
68+
expand(BitstrMeta, Fun, T, EAcc, EE, OriginalE, alignment(Alignment, EAlignment), RequireSize);
69+
expand(BitstrMeta, Fun, [{_, Meta, _} = H | T], Acc, E, OriginalE, Alignment, RequireSize) ->
7070
{Expr, ES} = expand_expr(Meta, H, Fun, E),
7171
{EAcc, EAlignment} = wrap_expr(Expr, Acc),
72-
expand(BitstrMeta, Fun, T, EAcc, ES, alignment(Alignment, EAlignment), RequireSize);
73-
expand(Meta, Fun, [H | T], Acc, E, Alignment, RequireSize) ->
72+
expand(BitstrMeta, Fun, T, EAcc, ES, OriginalE, alignment(Alignment, EAlignment), RequireSize);
73+
expand(Meta, Fun, [H | T], Acc, E, OriginalE, Alignment, RequireSize) ->
7474
{Expr, ES} = expand_expr(Meta, H, Fun, E),
7575
{EAcc, EAlignment} = wrap_expr(Expr, Acc),
76-
expand(Meta, Fun, T, EAcc, ES, alignment(Alignment, EAlignment), RequireSize).
76+
expand(Meta, Fun, T, EAcc, ES, OriginalE, alignment(Alignment, EAlignment), RequireSize).
7777

7878
prepend_unless_bitstring_in_match(Type, Meta, Left, Right, Acc, E) ->
7979
Expr = {'::', Meta, [Left, Right]},
@@ -157,15 +157,15 @@ env_for_error(E) -> E.
157157

158158
%% Expands and normalizes types of a bitstring.
159159

160-
expand_specs(ExprType, Meta, Info, E, RequireSize) ->
160+
expand_specs(ExprType, Meta, Info, E, OriginalE, RequireSize) ->
161161
Default =
162162
#{size => default,
163163
unit => default,
164164
sign => default,
165165
type => default,
166166
endianness => default},
167167
{#{size := Size, unit := Unit, type := Type, endianness := Endianness, sign := Sign}, ES} =
168-
expand_each_spec(Meta, unpack_specs(Info, []), Default, E),
168+
expand_each_spec(Meta, unpack_specs(Info, []), Default, E, OriginalE),
169169
MergedType = type(Meta, ExprType, Type, E),
170170
validate_size_required(Meta, RequireSize, ExprType, MergedType, Size, ES),
171171
SizeAndUnit = size_and_unit(Meta, ExprType, Size, Unit, ES),
@@ -190,30 +190,30 @@ type(_, default, Type, _) ->
190190
type(Meta, Other, Value, E) ->
191191
form_error(Meta, ?key(E, file), ?MODULE, {bittype_mismatch, Value, Other, type}).
192192

193-
expand_each_spec(Meta, [{Expr, _, Args} = H | T], Map, E) when is_atom(Expr) ->
193+
expand_each_spec(Meta, [{Expr, _, Args} = H | T], Map, E, OriginalE) when is_atom(Expr) ->
194194
case validate_spec(Expr, Args) of
195195
{Key, Arg} ->
196196
{Value, EE} = expand_spec_arg(Arg, E),
197-
validate_spec_arg(Meta, Key, Value, EE),
197+
validate_spec_arg(Meta, Key, Value, EE, OriginalE),
198198

199199
case maps:get(Key, Map) of
200200
default -> ok;
201201
Value -> ok;
202202
Other -> form_error(Meta, ?key(E, file), ?MODULE, {bittype_mismatch, Value, Other, Key})
203203
end,
204204

205-
expand_each_spec(Meta, T, maps:put(Key, Value, Map), EE);
205+
expand_each_spec(Meta, T, maps:put(Key, Value, Map), EE, OriginalE);
206206
none ->
207207
case 'Elixir.Macro':expand(H, elixir_env:linify({?line(Meta), E})) of
208208
H ->
209209
form_error(Meta, ?key(E, file), ?MODULE, {undefined_bittype, H});
210210
NewTypes ->
211-
expand_each_spec(Meta, unpack_specs(NewTypes, []) ++ T, Map, E)
211+
expand_each_spec(Meta, unpack_specs(NewTypes, []) ++ T, Map, E, OriginalE)
212212
end
213213
end;
214-
expand_each_spec(Meta, [Expr | _], _Map, E) ->
214+
expand_each_spec(Meta, [Expr | _], _Map, E, _OriginalE) ->
215215
form_error(Meta, ?key(E, file), ?MODULE, {undefined_bittype, Expr});
216-
expand_each_spec(_Meta, [], Map, E) ->
216+
expand_each_spec(_Meta, [], Map, E, _OriginalE) ->
217217
{Map, E}.
218218

219219
unpack_specs({'-', _, [H, T]}, Acc) ->
@@ -251,17 +251,33 @@ validate_spec(_, _) -> none.
251251
expand_spec_arg(Expr, E) when is_atom(Expr); is_integer(Expr) -> {Expr, E};
252252
expand_spec_arg(Expr, E) -> elixir_expand:expand(Expr, E).
253253

254-
validate_spec_arg(Meta, size, Value, E) ->
254+
validate_spec_arg(Meta, size, Value, E, OriginalE) ->
255255
case Value of
256-
{Var, _, Context} when is_atom(Var) and is_atom(Context) -> ok;
257-
_ when is_integer(Value) -> ok;
258-
_ -> form_error(Meta, ?key(E, file), ?MODULE, {bad_size_argument, Value})
256+
{Var, _, Context} when is_atom(Var) and is_atom(Context) ->
257+
case is_valid_spec_arg_var({Var, Context}, E, OriginalE) of
258+
true -> ok;
259+
false -> form_error(Meta, ?key(E, file), ?MODULE, {undefined_var_in_spec, Value})
260+
end;
261+
_ when is_integer(Value) ->
262+
ok;
263+
_ ->
264+
form_error(Meta, ?key(E, file), ?MODULE, {bad_size_argument, Value})
259265
end;
260-
validate_spec_arg(Meta, unit, Value, E) when not is_integer(Value) ->
266+
validate_spec_arg(Meta, unit, Value, E, _OriginalE) when not is_integer(Value) ->
261267
form_error(Meta, ?key(E, file), ?MODULE, {bad_unit_argument, Value});
262-
validate_spec_arg(_Meta, _Key, _Value, _E) ->
268+
validate_spec_arg(_Meta, _Key, _Value, _E, _OriginalE) ->
263269
ok.
264270

271+
is_valid_spec_arg_var(Var, E, #{context := match} = OriginalE) ->
272+
case ?key(OriginalE, prematch_vars) of
273+
#{Var := _} ->
274+
true;
275+
_ ->
276+
maps:is_key(Var, ?key(E, current_vars)) andalso
277+
not maps:is_key(Var, ?key(OriginalE, current_vars))
278+
end;
279+
is_valid_spec_arg_var(_Var, _E, _OriginalE) -> true.
280+
265281
validate_size_required(Meta, true, default, Type, default, E) when Type == binary; Type == bitstring ->
266282
form_error(Meta, ?key(E, file), ?MODULE, unsized_binary);
267283
validate_size_required(_, _, _, _, _, _) ->
@@ -375,4 +391,10 @@ format_error({nested_match, Expr}) ->
375391
Message =
376392
"cannot pattern match inside a bitstring "
377393
"that is already in match, got: ~ts",
378-
io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]).
394+
io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]);
395+
format_error({undefined_var_in_spec, Var}) ->
396+
Message =
397+
"undefined variable \"~ts\" in bitstring segment. If the size of the binary is a "
398+
"variable, the variable must be defined prior to its use in the binary/bitstring match "
399+
"itself, or outside the pattern match",
400+
io_lib:format(Message, ['Elixir.Macro':to_string(Var)]).

lib/elixir/test/elixir/kernel/expansion_test.exs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,6 +2394,24 @@ defmodule Kernel.ExpansionTest do
23942394
expand(code)
23952395
end
23962396

2397+
assert_raise CompileError, ~r/undefined variable "foo"/, fn ->
2398+
code =
2399+
quote do
2400+
fn <<_::size(foo), foo::size(8)>> -> :ok end
2401+
end
2402+
2403+
expand(code)
2404+
end
2405+
2406+
assert_raise CompileError, ~r/undefined variable "foo" in bitstring segment/, fn ->
2407+
code =
2408+
quote do
2409+
fn foo, <<_::size(foo)>> -> :ok end
2410+
end
2411+
2412+
expand(code)
2413+
end
2414+
23972415
message = ~r"size in bitstring expects an integer or a variable as argument, got: foo()"
23982416

23992417
assert_raise CompileError, message, fn ->

0 commit comments

Comments
 (0)