Skip to content

ahrefs/ocaml-callgraph

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OCaml Call Graph

Static analysis tool for generating function call graphs from OCaml source code.

This tool analyzes compiled type annotation (.cmt) files to trace function calls and generates graphviz-compatible DOT output for visualization.

New: Emacs integration with interactive hierarchical tree view and jump-to-definition!

Features

  • Function-centric analysis: Start from a named function and recursively follow the call chain
  • Interactive Emacs integration: Hierarchical tree view with fold/unfold and jump-to-definition
  • Dynamic external library detection: Automatically queries opam switch to detect ALL installed packages
  • Configurable filtering of standard library and external dependencies
  • Multiple output formats: DOT (Graphviz), Graph::Easy (human-readable text), and Org-mode (hierarchical tree)
  • Clean output separation: Debug logs to stderr, graph to stdout
  • Pure compiler-libs implementation - minimal dependencies

Installation

# Clone the repository
git clone https://github.com/koonwen/ocaml-callgraph.git
cd ocaml-callgraph

# Build with dune
dune build

# Install binaries (optional)
dune install

Quick Start

1. Compile Your OCaml Code with Type Annotations

ocamlopt -bin-annot -c myfile.ml

This generates myfile.cmt which contains the typed AST needed for analysis.

2. Generate a Call Graph

# Basic usage - analyze one function (DOT format by default)
# You can provide either .cmt or .ml file (auto-discovers .cmt in _build/)
dune exec -- cg my_function myfile.ml > graph.dot

# Filter out stdlib functions for cleaner output
dune exec -- cg --no-stdlib my_function myfile.ml > graph.dot

# Show only local functions (exclude stdlib and external libraries)
dune exec -- cg --no-stdlib --no-external my_function myfile.ml > graph.dot

# Output in Graph::Easy format (ASCII-friendly text format)
dune exec -- cg --format grapheasy my_function myfile.ml > graph.txt

# Output in Org-mode format for Emacs
dune exec -- cg --format org --no-stdlib my_function myfile.ml > callgraph.org

# Pipe directly to graphviz (debug logs go to stderr)
dune exec -- cg --no-stdlib my_function myfile.ml 2>/dev/null | dot -Tpng > graph.png

# You can still use .cmt files directly if needed
dune exec -- cg my_function _build/default/myfile.cmt > graph.dot

3. Visualize the Graph

Using Graphviz (DOT format)

# Generate PNG
dot -Tpng graph.dot -o graph.png

# Generate SVG
dot -Tsvg graph.dot -o graph.svg

# View directly (Linux)
xdg-open graph.png

# Or use online viewers
# https://dreampuf.github.io/GraphvizOnline/
# https://edotor.net/

Using Graph::Easy (text format)

# The Graph::Easy format is human-readable as-is:
cat graph.txt

# Or render as ASCII art (requires Graph::Easy perl module):
graph-easy graph.txt

# Convert to other formats:
graph-easy graph.txt --as boxart
graph-easy graph.txt --as svg > graph.svg

Note: Graph::Easy's ASCII layouter works best for simple, relatively linear call graphs. For complex graphs with many cycles and interconnections, the layouter may give up and not render all nodes/edges. In such cases, use DOT format with Graphviz for better results.

Using Org-mode (Emacs)

# Generate org-mode hierarchical tree
cg --format org --no-stdlib my_function file.cmt > callgraph.org

# Open in Emacs
emacs callgraph.org

The org-mode format creates a hierarchical, foldable tree structure:

  • Click on function links to jump to their definitions
  • Use TAB to fold/unfold sections
  • Navigate with standard org-mode keybindings
  • Cycle detection shows (cycle) indicators to prevent infinite recursion

Example output:

* main [[file:example.ml::10][main]]
** process_data [[file:example.ml::15][process_data]]
*** helper_function [[file:example.ml::5][helper_function]]
** another_helper [[file:example.ml::20][another_helper]]
*** helper_function [[file:example.ml::5][helper_function]]

Usage

The cg tool performs function-centric call graph analysis:

cg [OPTIONS] <function_name> <file.cmt|file.ml>

Options:
  --no-stdlib       Exclude Stdlib.* functions (List.map, Printf.printf, +, -, etc.)
  --no-external     Exclude external opam packages (dynamically detected from opam switch)
  --format FORMAT   Output format: 'dot' (default), 'grapheasy', or 'org'
  --emit-locations  Emit function location information to stderr (file:line:col)
  --help, -h        Show help message

Formats:
  dot               Graphviz DOT format (default) - for visualization with graphviz
  grapheasy         Graph::Easy text format - human-readable, can be rendered as ASCII art
  org               Org-mode hierarchical tree - for Emacs org-mode with jump-to-definition

Input File:
  You can provide either a .cmt file directly or a .ml file.
  When given a .ml file, the tool automatically searches for the
  corresponding .cmt file in the _build/default/ directory.

Features:

  • Starts from a named function and recursively follows call chain
  • Automatic .cmt discovery: Provide .ml files, tool finds .cmt in _build/ automatically
  • Dynamic opam package detection: Queries your opam switch to detect ALL external libraries
  • Configurable filtering to remove noise from stdlib and external packages
  • Separate debug output (stderr) doesn't pollute graph (stdout)
  • Handles cycles gracefully with visited function tracking
  • Perfect for piping to graphviz

Output Format Recommendations:

  • DOT format (default): Best for all graphs, especially complex ones with cycles. Use with Graphviz for visualization.
  • Graph::Easy format: Best for simple, relatively linear call graphs. ASCII art generation works well for small graphs but may fail on complex graphs with many cycles and interconnections.
  • Org format: Best for Emacs users. Creates a hierarchical, foldable tree with clickable links to jump to function definitions. Works well with org-mode's navigation features.

Example:

# Analyze 'process_request' function, show only local calls (DOT format)
cg --no-stdlib --no-external process_request server.cmt | dot -Tpng > graph.png

# For simple graphs, Graph::Easy format provides nice ASCII art
cg --format grapheasy --no-stdlib simple_function file.cmt | graph-easy

# Generate org-mode hierarchical tree for Emacs (automatically enables location tracking)
cg --format org --no-stdlib my_function file.cmt > callgraph.org

# Emit location metadata for editor integration (jump-to-definition)
cg --emit-locations my_function file.cmt 2> locations.txt > graph.dot

Location Metadata

The --emit-locations flag outputs function location information to stderr in the format:

function_name:file:line:col

This metadata can be used for editor integration, allowing jump-to-definition functionality when viewing call graphs. Location output is separated to stderr so it doesn't interfere with the graph output on stdout.

Example:

# Capture both graph and locations
cg --emit-locations my_function file.cmt > graph.dot 2> locations.txt

# The locations file will contain:
# my_function:file.ml:10:4
# helper_function:file.ml:5:4
# process_data:file.ml:15:4

Emacs Integration

An Emacs package is provided for interactive call graph viewing with jump-to-definition support.

Features:

  • Hierarchical tree view with the hierarchy package (recommended!)
  • View call graphs in Emacs buffer with ASCII art rendering
  • Generate org-mode call graphs for documentation
  • Click on function names to jump to definitions
  • Fold/unfold tree nodes with TAB
  • Toggle filtering with hotkeys (s for stdlib, e for external)
  • Automatic function name and .cmt file inference
  • Automatic location tracking

Quick start:

;; In ~/.emacs.d/init.el
(add-to-list 'load-path "/path/to/ocaml-callgraph/emacs")
(require 'ocaml-callgraph)

;; Optional: Add keybindings
(global-set-key (kbd "C-c g h") 'ocaml-callgraph-generate-hierarchy)  ; Hierarchy tree (recommended)
(global-set-key (kbd "C-c g g") 'ocaml-callgraph-generate)            ; ASCII graph viewer
(global-set-key (kbd "C-c g o") 'ocaml-callgraph-generate-org)        ; Org-mode file

Usage:

;; Interactive hierarchy tree viewer (RECOMMENDED)
M-x ocaml-callgraph-generate-hierarchy RET

;; Interactive graph viewer with ASCII art
M-x ocaml-callgraph-generate RET

;; Org-mode hierarchical call graph (creates a file)
M-x ocaml-callgraph-generate-org RET

;; All commands automatically infer function name and .cmt file location
;; Use C-u prefix to manually specify: C-u M-x ocaml-callgraph-generate-hierarchy RET

Hierarchy Tree Features (recommended):

  • Interactive, foldable tree structure (no external files needed)
  • Press TAB to fold/unfold nodes
  • Click or press RET to jump to function definitions
  • Press g to regenerate after changing filters
  • Press s/e to toggle stdlib/external filtering
  • All in a single Emacs buffer - fast and responsive

Org-mode Features:

  • Hierarchical, foldable tree structure
  • Click on [[file:...]] links to jump to definitions
  • Use TAB to fold/unfold sections
  • Navigate with standard org-mode keybindings
  • Saved to project root as callgraph-<function-name>.org
  • Good for documentation and sharing

See emacs/EMACS.md and HIERARCHY_FEATURE.md for detailed documentation.

Examples

The examples/ directory contains sample OCaml files demonstrating various use cases:

cd examples

# Build examples and generate graphs
make graphs

# Generate PNG images
make images

# Generate only local function graphs
make graphs FILTER='--no-stdlib --no-external'

# See all options
make help

See examples/README.md for detailed examples.

How It Works

The tool uses OCaml's compiler-libs to analyze typed AST:

  1. Query opam switch to get list of installed packages (cached on first use)
  2. Read .cmt files using Cmt_format.read_cmt
  3. Find the starting function in the typed AST
  4. Traverse typed AST (Typedtree) to extract function calls
  5. Pattern match on function calls (Texp_apply)
  6. Extract function paths using Path.name
  7. Classify each function as local, stdlib, or external (using opam package list)
  8. Recursively process callees (with cycle detection)
  9. Output call graph as DOT format

Filtering Behavior

The --no-stdlib and --no-external flags help focus on relevant code:

Stdlib Functions

Functions from OCaml's standard library (always have Stdlib. prefix):

  • Stdlib.List.map
  • Stdlib.Printf.printf
  • Stdlib.+, Stdlib.-, Stdlib.* (operators)

External Library Functions

Functions from opam packages (dynamically detected by querying opam list --installed):

  • Base.List.map
  • Lwt.bind
  • Yojson.Basic.from_string
  • Core.String.split
  • Any other package installed in your current opam switch

Local Functions

Functions defined in your .cmt file:

  • my_helper
  • process_data
  • handle_request

Troubleshooting

Having issues? See the Troubleshooting Guide.

Emacs users: Run M-x ocaml-callgraph-diagnose for quick diagnostics.

Common issues:

  • Permission denied: Check executable permissions or set custom org directory
  • Cannot find executable: Install with dune install or set full path
  • No .cmt file found: Run dune build first

Requirements

  • OCaml >= 4.14
  • Dune >= 3.16
  • opam (for dynamic external library detection)
  • Graphviz (optional, for visualization)

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

MIT License - see LICENSE file for details.

Author

Koon Wen Lee

Links

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published