Reach.Plugin behaviour (Reach v2.2.0)

Copy Markdown View Source

Behaviour for library-specific analysis plugins.

Plugins extend Reach in three ways:

  1. Graph edgesanalyze/2 and analyze_project/3 add domain-specific edges to the dependence graph (framework dispatch, message routing, etc.)

  2. Effect classificationclassify_effect/1 teaches the effect classifier about framework-specific calls (Ecto queries are pure, Repo writes are :write, etc.)

  3. Embedded IRanalyze_embedded/2 extracts code from string literals (e.g. JS inside QuickBEAM.eval) and returns additional IR nodes plus cross-language edges.

  4. Framework presentation/patterns — optional callbacks provide framework-specific trace presets, behaviour labels, and visualization edge filtering.

Implementing a plugin

defmodule MyPlugin do
  @behaviour Reach.Plugin

  @impl true
  def analyze(all_nodes, _opts), do: []

  @impl true
  def classify_effect(%Reach.IR.Node{type: :call, meta: %{function: :my_pure_fn}}), do: :pure
  def classify_effect(_), do: nil
end

Built-in plugins

Plugins for Phoenix, Ecto, Oban, GenStage, Jido, and OpenTelemetry are included and auto-detected at runtime. Override with the :plugins option:

Reach.Project.from_mix_project(plugins: [Reach.Plugins.Ecto])

Disable auto-detection:

Reach.string_to_graph!(source, plugins: [])

Summary

Callbacks

Analyzes IR nodes from a single module and returns edges to add.

Extracts embedded code from IR nodes (e.g. JS strings passed to QuickBEAM.eval) and returns additional IR nodes plus edges connecting them to the host graph.

Analyzes IR nodes across all modules in a project.

Classifies the effect of a call node.

Functions

Infers a framework-specific behaviour label from callback names.

Asks each plugin to classify a call node's effect.

Returns the list of auto-detected plugins based on loaded dependencies.

Returns true when a plugin marks a call-graph edge as visualization noise.

Resolves plugins from options, falling back to auto-detection.

Runs module-local analysis hooks for the configured plugins.

Runs embedded-node analysis hooks for plugins that provide them.

Runs project-level analysis hooks for plugins that provide them.

Compiles a framework-specific trace pattern, if a plugin recognizes it.

Types

edge_spec()

@type edge_spec() :: {Reach.IR.Node.id(), Reach.IR.Node.id(), term()}

embedded_result()

@type embedded_result() :: {[Reach.IR.Node.t()], [edge_spec()]}

Callbacks

analyze(all_nodes, opts)

@callback analyze(all_nodes :: [Reach.IR.Node.t()], opts :: keyword()) :: [edge_spec()]

Analyzes IR nodes from a single module and returns edges to add.

analyze_embedded(all_nodes, opts)

(optional)
@callback analyze_embedded(all_nodes :: [Reach.IR.Node.t()], opts :: keyword()) ::
  embedded_result()

Extracts embedded code from IR nodes (e.g. JS strings passed to QuickBEAM.eval) and returns additional IR nodes plus edges connecting them to the host graph.

analyze_project(modules, all_nodes, opts)

(optional)
@callback analyze_project(
  modules :: %{required(module()) => map()},
  all_nodes :: [Reach.IR.Node.t()],
  opts :: keyword()
) :: [edge_spec()]

Analyzes IR nodes across all modules in a project.

Only needed for cross-module patterns like router→controller dispatch or job enqueue→perform flow.

behaviour_label(callbacks)

(optional)
@callback behaviour_label(callbacks :: [atom()]) :: String.t() | nil

classify_effect(node)

(optional)
@callback classify_effect(node :: Reach.IR.Node.t()) :: atom() | nil

Classifies the effect of a call node.

Return an effect atom (:pure, :read, :write, :io, :send, :exception) or nil to defer to the next classifier.

ignore_call_edge?(t)

(optional)
@callback ignore_call_edge?(Graph.Edge.t()) :: boolean()

trace_pattern(pattern)

(optional)
@callback trace_pattern(pattern :: String.t()) :: (Reach.IR.Node.t() -> boolean()) | nil

Functions

behaviour_label(plugins, callbacks)

Infers a framework-specific behaviour label from callback names.

classify_effect(plugins, node)

Asks each plugin to classify a call node's effect.

Returns the first non-nil result, or nil if no plugin matches.

detect()

Returns the list of auto-detected plugins based on loaded dependencies.

ignore_call_edge?(plugins, edge)

Returns true when a plugin marks a call-graph edge as visualization noise.

resolve(opts)

Resolves plugins from options, falling back to auto-detection.

run_analyze(plugins, all_nodes, opts)

Runs module-local analysis hooks for the configured plugins.

run_analyze_embedded(plugins, all_nodes, opts)

Runs embedded-node analysis hooks for plugins that provide them.

run_analyze_project(plugins, modules, all_nodes, opts)

Runs project-level analysis hooks for plugins that provide them.

trace_pattern(plugins, pattern)

Compiles a framework-specific trace pattern, if a plugin recognizes it.