Skip to content

Commit 8869177

Browse files
author
José Valim
committed
Ensure we can escape bitstrings, add Macro.validate/1
1 parent f29889d commit 8869177

File tree

5 files changed

+79
-1
lines changed

5 files changed

+79
-1
lines changed

lib/elixir/lib/macro.ex

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,40 @@ defmodule Macro do
305305
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
306306
end
307307

308+
@doc """
309+
Validates the given expressions are valid quoted expressions.
310+
311+
Check the `type:Macro.t` for the specification of a valid
312+
quoted expression.
313+
"""
314+
@spec validate(term) :: :ok | {:error, term}
315+
def validate(expr) do
316+
find_invalid(expr) || :ok
317+
end
318+
319+
defp find_invalid({left, right}), do:
320+
find_invalid(left) || find_invalid(right)
321+
322+
defp find_invalid({left, meta, right}) when is_list(meta) and (is_atom(right) or is_list(right)), do:
323+
find_invalid(left) || find_invalid(right)
324+
325+
defp find_invalid(list) when is_list(list), do:
326+
Enum.find_value(list, &find_invalid/1)
327+
328+
defp find_invalid(pid) when is_pid(pid), do: nil
329+
defp find_invalid(atom) when is_atom(atom), do: nil
330+
defp find_invalid(num) when is_number(num), do: nil
331+
defp find_invalid(bin) when is_binary(bin), do: nil
332+
333+
defp find_invalid(fun) when is_function(fun) do
334+
unless :erlang.fun_info(fun, :env) == {:env, []} and
335+
:erlang.fun_info(fun, :type) == {:type, :external} do
336+
{:error, fun}
337+
end
338+
end
339+
340+
defp find_invalid(other), do: {:error, other}
341+
308342
@doc ~S"""
309343
Unescape the given chars.
310344

lib/elixir/src/elixir_exp.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ expand({_, Meta, Args} = Invalid, E) when is_list(Meta) and is_list(Args) ->
354354

355355
expand({_, _, _} = Tuple, E) ->
356356
compile_error([{line,0}], ?m(E, file), "invalid quoted expression: ~ts",
357-
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
357+
['Elixir.Kernel':inspect(Tuple, [])]);
358358

359359
%% Literals
360360

lib/elixir/src/elixir_quote.erl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,15 @@ do_quote({Left, Right}, Q, E) ->
242242
{TRight, RQ} = do_quote(Right, LQ, E),
243243
{{TLeft, TRight}, RQ};
244244

245+
do_quote(BitString, #elixir_quote{escape=true} = Q, E) when is_bitstring(BitString) ->
246+
case bit_size(BitString) rem 8 of
247+
0 ->
248+
{BitString, Q};
249+
Size ->
250+
<<Bits:Size, Bytes/binary>> = BitString,
251+
{{'<<>>', [], [{'::', [], [Bits, Size]}, Bytes]}, Q}
252+
end;
253+
245254
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
246255
{TT, TQ} = do_quote(maps:to_list(Map), Q, E),
247256
{{'%{}', [], TT}, TQ};
@@ -259,6 +268,7 @@ do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
259268
{TR, QR} = do_quote(R, QL, E),
260269
{update_last(TL, fun(X) -> {'|', [], [X, TR]} end), QR}
261270
end;
271+
262272
do_quote(List, Q, E) when is_list(List) ->
263273
do_splice(lists:reverse(List), Q, E);
264274

lib/elixir/test/elixir/kernel_test.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ defmodule KernelTest do
8989
assert case_in(-3, -1..-3) == true
9090
end
9191

92+
@bitstring <<"foo", 16::4>>
93+
94+
test "bitstring attribute" do
95+
assert @bitstring == <<"foo", 16::4>>
96+
end
97+
9298
test "paren as nil" do
9399
assert nil?(()) == true
94100
assert ((); ();) == nil

lib/elixir/test/elixir/macro_test.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ defmodule MacroTest do
4242
assert {:%{}, [], [a: 1]} = Macro.escape(%{a: 1})
4343
end
4444

45+
test :escape_handles_bitstring do
46+
assert {:<<>>, [], [{:::, [], [1, 4]}, ","]} == Macro.escape(<<300::12>>)
47+
end
48+
4549
test :escape_works_recursively do
4650
assert [1,{:{}, [], [:a,:b,:c]}, 3] == Macro.escape([1, {:a, :b, :c}, 3])
4751
end
@@ -445,6 +449,30 @@ defmodule MacroTest do
445449
"::Bar:.foo(:1:, :2:, :3:):"
446450
end
447451

452+
## validate
453+
454+
test :validate do
455+
ref = make_ref()
456+
457+
assert Macro.validate(1) == :ok
458+
assert Macro.validate(1.0) == :ok
459+
assert Macro.validate(:foo) == :ok
460+
assert Macro.validate("bar") == :ok
461+
assert Macro.validate(self()) == :ok
462+
assert Macro.validate({1, 2}) == :ok
463+
assert Macro.validate({:foo, [], :baz}) == :ok
464+
assert Macro.validate({:foo, [], []}) == :ok
465+
assert Macro.validate([1, 2, 3]) == :ok
466+
467+
assert Macro.validate(<<0::4>>) == {:error, <<0::4>>}
468+
assert Macro.validate(ref) == {:error, ref}
469+
assert Macro.validate({1, ref}) == {:error, ref}
470+
assert Macro.validate({ref, 2}) == {:error, ref}
471+
assert Macro.validate([1, ref, 3]) == {:error, ref}
472+
assert Macro.validate({:foo, [], 0}) == {:error, {:foo, [], 0}}
473+
assert Macro.validate({:foo, 0, []}) == {:error, {:foo, 0, []}}
474+
end
475+
448476
## decompose_call
449477

450478
test :decompose_call do

0 commit comments

Comments
 (0)