Skip to content

Commit cc1d7c5

Browse files
committed
Disallow mixed use of strings and atoms in record initialization/update
This allows provides better overall performance to these procedures.
1 parent 7649a5d commit cc1d7c5

File tree

2 files changed

+33
-25
lines changed

2 files changed

+33
-25
lines changed

lib/elixir/lib/record.ex

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,10 @@ defmodule Record do
140140
there is more work happening at runtime.
141141
142142
The above calls (new and update) can interchangeably accept both
143-
atom and string keys for field names. Please note, however, that
144-
atom keys are faster.
143+
atom and string keys for field names, however not both at the same time.
144+
Please also note that atom keys are faster. This feature allows to
145+
"sanitize" untrusted dictionaries and initialize/update records without
146+
using `binary_to_existing_atom/1`.
145147
146148
To sum up, `defrecordp` should be used when you don't want
147149
to expose the record information while `defrecord` should be used
@@ -628,16 +630,15 @@ defmodule Record do
628630
# an ordered dict of options (opts) and it will try to fetch
629631
# the given key from the ordered dict, falling back to the
630632
# default value if one does not exist.
631-
selective = lc { k, v } inlist values do
632-
string_k = atom_to_binary(k)
633-
quote do
633+
atom_selective = lc { k, v } inlist values do
634+
quote do: Keyword.get(opts, unquote(k), unquote(v))
635+
end
636+
string_selective = lc { k, v } inlist values do
637+
k = atom_to_binary(k)
638+
quote do
634639
case :lists.keyfind(unquote(k), 1, opts) do
635-
false ->
636-
case :lists.keyfind(unquote(string_k), 1, opts) do
637-
false -> unquote(v)
638-
{_, value} -> value
639-
end
640-
{_, value} -> value
640+
false -> unquote(v)
641+
{_, v} -> v
641642
end
642643
end
643644
end
@@ -648,7 +649,8 @@ defmodule Record do
648649
649650
@doc false
650651
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
651-
def new(opts) when is_list(opts), do: { __MODULE__, unquote_splicing(selective) }
652+
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
653+
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
652654
end
653655
end
654656
@@ -739,32 +741,38 @@ defmodule Record do
739741
# Define an updater method that receives a
740742
# keyword list and updates the record.
741743
defp updater(values) do
742-
fields =
744+
atom_fields =
743745
lc {key, _default} inlist values do
744-
string_key = atom_to_binary(key)
745746
index = find_index(values, key, 1)
747+
quote do: Keyword.get(keywords, unquote(key), elem(record, unquote(index)))
748+
end
749+
750+
string_fields =
751+
lc {key, _default} inlist values do
752+
index = find_index(values, key, 1)
753+
key = atom_to_binary(key)
746754
quote do
747755
case :lists.keyfind(unquote(key), 1, keywords) do
748-
false ->
749-
case :lists.keyfind(unquote(string_key), 1, keywords) do
750-
false -> elem(record, unquote(index))
751-
{_, value} -> value
752-
end
756+
false -> elem(record, unquote(index))
753757
{_, value} -> value
754758
end
755759
end
756760
end
757761
758-
contents = quote do: { __MODULE__, unquote_splicing(fields) }
762+
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
763+
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
759764
760765
quote do
761766
@doc false
762767
def update([], record) do
763768
record
764769
end
765770
766-
def update(keywords, record) do
767-
unquote(contents)
771+
def update([{key, _}|_] = keywords, record) when is_atom(key) do
772+
unquote(atom_contents)
773+
end
774+
def update([{key, _}|_] = keywords, record) when is_binary(key) do
775+
unquote(string_contents)
768776
end
769777
end
770778
end

lib/elixir/test/elixir/record_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,16 +165,16 @@ defmodule RecordTest do
165165
end
166166

167167
test :string_names do
168-
a = RecordTest.FooTest.new([{:foo, 1}, {"bar", 1}])
168+
a = RecordTest.FooTest.new([{"foo", 1}, {"bar", 1}])
169169
assert a.foo == 1
170170
assert a.bar == 1
171-
a = a.update([{"foo", 2}, {:bar, 2}])
171+
a = a.update([{"foo", 2}, {"bar", 2}])
172172
assert a.foo == 2
173173
assert a.bar == 2
174174
end
175175

176176
test :string_names_import do
177-
record = RecordTest.FileInfo.new([{"type", :regular}, {:access, 100}])
177+
record = RecordTest.FileInfo.new([{"type", :regular}, {"access", 100}])
178178
assert record.type == :regular
179179
assert record.access == 100
180180
assert record.update([{"access", 101}]).access == 101

0 commit comments

Comments
 (0)