diff --git a/lib/reach/project.ex b/lib/reach/project.ex index e733ee5..07ef902 100644 --- a/lib/reach/project.ex +++ b/lib/reach/project.ex @@ -31,11 +31,20 @@ defmodule Reach.Project do nodes: %{IR.Node.id() => IR.Node.t()}, call_graph: Graph.t(), summaries: %{{module(), atom(), non_neg_integer()} => map()}, - plugins: [module()] + plugins: [module()], + cache_key: reference() | nil } @enforce_keys [:modules, :graph, :nodes, :call_graph] - defstruct [:modules, :graph, :nodes, :call_graph, summaries: %{}, plugins: []] + defstruct [ + :modules, + :graph, + :nodes, + :call_graph, + summaries: %{}, + plugins: [], + cache_key: nil + ] @doc """ Builds a project graph from source file paths. @@ -312,7 +321,8 @@ defmodule Reach.Project do graph: Graph.new(type: :directed), nodes: nodes, call_graph: Graph.new(type: :directed), - plugins: plugins + plugins: plugins, + cache_key: make_ref() } end @@ -403,7 +413,8 @@ defmodule Reach.Project do nodes: merged_nodes, call_graph: merged_call_graph, summaries: summaries, - plugins: plugins + plugins: plugins, + cache_key: make_ref() } end diff --git a/lib/reach/project/query.ex b/lib/reach/project/query.ex index d97884e..2d0385e 100644 --- a/lib/reach/project/query.ex +++ b/lib/reach/project/query.ex @@ -3,9 +3,24 @@ defmodule Reach.Project.Query do alias Reach.IR.Helpers, as: IRHelpers - def function_index(project), do: build_function_index(project) + @function_index_cache_key {__MODULE__, :function_index} - def reset_cache, do: :ok + def function_index(%{cache_key: key} = project) do + case Process.get(@function_index_cache_key) do + {^key, index} -> + index + + _miss -> + index = build_function_index(project) + Process.put(@function_index_cache_key, {key, index}) + index + end + end + + def reset_cache do + Process.delete(@function_index_cache_key) + :ok + end def find_function(project, target) do index = function_index(project) diff --git a/test/reach/project/query_cache_test.exs b/test/reach/project/query_cache_test.exs index c87f77e..0e348f3 100644 --- a/test/reach/project/query_cache_test.exs +++ b/test/reach/project/query_cache_test.exs @@ -15,6 +15,25 @@ defmodule Reach.Project.QueryCacheTest do refute Query.find_function(second, {FirstOnly, :run, 0}) end + test "function_index is memoized within a process for the same project" do + project = project_with("Memoized", "go") + + first = Query.function_index(project) + second = Query.function_index(project) + + assert :erts_debug.same(first, second) + end + + test "reset_cache invalidates the cached function index" do + project = project_with("Resettable", "do_thing") + + first = Query.function_index(project) + Query.reset_cache() + second = Query.function_index(project) + + refute :erts_debug.same(first, second) + end + defp project_with(module, function) do source = """ defmodule #{module} do