zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

unsafe_to_atom.ex (3032B)


      1 defmodule Credo.Check.Warning.UnsafeToAtom do
      2   use Credo.Check,
      3     base_priority: :high,
      4     category: :warning,
      5     tags: [:controversial],
      6     explanations: [
      7       check: """
      8       Creating atoms from unknown or external sources dynamically is a potentially
      9       unsafe operation because atoms are not garbage-collected by the runtime.
     10 
     11       Creating an atom from a string or charlist should be done by using
     12 
     13           String.to_existing_atom(string)
     14 
     15       or
     16 
     17           List.to_existing_atom(charlist)
     18 
     19       Module aliases should be constructed using
     20 
     21           Module.safe_concat(prefix, suffix)
     22 
     23       or
     24 
     25           Module.safe_concat([prefix, infix, suffix])
     26 
     27       Jason.decode/Jason.decode! should be called using `keys: :atoms!` (*not* `keys: :atoms`):
     28 
     29           Jason.decode(str, keys: :atoms!)
     30 
     31       or `:keys` should be omitted (which defaults to `:strings`):
     32 
     33           Jason.decode(str)
     34 
     35       """
     36     ]
     37 
     38   @doc false
     39   @impl true
     40   def run(%SourceFile{} = source_file, params) do
     41     issue_meta = IssueMeta.for(source_file, params)
     42 
     43     Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
     44   end
     45 
     46   defp traverse({:@, _, _}, issues, _) do
     47     {nil, issues}
     48   end
     49 
     50   defp traverse({{:., _loc, call}, meta, args} = ast, issues, issue_meta) do
     51     case get_forbidden_call(call, args) do
     52       {bad, suggestion} ->
     53         {ast, issues_for_call(bad, suggestion, meta, issue_meta, issues)}
     54 
     55       nil ->
     56         {ast, issues}
     57     end
     58   end
     59 
     60   defp traverse(ast, issues, _issue_meta) do
     61     {ast, issues}
     62   end
     63 
     64   defp get_forbidden_call([:erlang, :list_to_atom], [_]) do
     65     {":erlang.list_to_atom/1", ":erlang.list_to_existing_atom/1"}
     66   end
     67 
     68   defp get_forbidden_call([:erlang, :binary_to_atom], [_, _]) do
     69     {":erlang.binary_to_atom/2", ":erlang.binary_to_existing_atom/2"}
     70   end
     71 
     72   defp get_forbidden_call([{:__aliases__, _, [:String]}, :to_atom], [_]) do
     73     {"String.to_atom/1", "String.to_existing_atom/1"}
     74   end
     75 
     76   defp get_forbidden_call([{:__aliases__, _, [:List]}, :to_atom], [_]) do
     77     {"List.to_atom/1", "List.to_existing_atom/1"}
     78   end
     79 
     80   defp get_forbidden_call([{:__aliases__, _, [:Module]}, :concat], [_]) do
     81     {"Module.concat/1", "Module.safe_concat/1"}
     82   end
     83 
     84   defp get_forbidden_call([{:__aliases__, _, [:Module]}, :concat], [_, _]) do
     85     {"Module.concat/2", "Module.safe_concat/2"}
     86   end
     87 
     88   defp get_forbidden_call([{:__aliases__, _, [:Jason]}, decode], args)
     89        when decode in [:decode, :decode!] do
     90     args
     91     |> Enum.any?(fn arg -> Keyword.keyword?(arg) and Keyword.get(arg, :keys) == :atoms end)
     92     |> if do
     93       {"Jason.#{decode}(..., keys: :atoms)", "Jason.#{decode}(..., keys: :atoms!)"}
     94     else
     95       nil
     96     end
     97   end
     98 
     99   defp get_forbidden_call(_, _) do
    100     nil
    101   end
    102 
    103   defp issues_for_call(call, suggestion, meta, issue_meta, issues) do
    104     options = [
    105       message: "Prefer #{suggestion} over #{call} to avoid creating atoms at runtime",
    106       trigger: call,
    107       line_no: meta[:line]
    108     ]
    109 
    110     [format_issue(issue_meta, options) | issues]
    111   end
    112 end