Last week I was doing some cleanup work (short holiday weeks are great for paying off technical debt), and was deleting some supposedly unused code. This was a pretty tedious process of running functions like slime-who-calls and slime-who-references , running git grep -i on the command line, and undefining functions in just the right order.

I’ve seen a lot of articles recently on static analysis of code, and spent some time playing with the introspection features of slime to identify unused code (short holiday weeks are also great for following a tangents). I ended up with a slow mudball of code that worked pretty well.

Warning, large images coming up.

The code itself is up on github, but there’s no ASDF system yet, so you have to load it manually:

(require :iterate) (require :alexandria) (require :swank) (load "~/lisp/static-analysis/static-analysis.lisp") (in-package :static-analysis)

An truncated example:

STATIC-ANALYSIS> (call-graph->dot :alexandria ) digraph g{ subgraph clusterG982{ label="PACKAGE STATIC-ANALYSIS" G983 [label="ENSURE-PACKAGE-LIST"] } subgraph clusterG949{ label="PACKAGE ALEXANDRIA.0.DEV" ... } G983 -> G995 ... G951 -> G950 } NIL

Here’s what it actually looks like:

The code currently scans all loaded code, and puts functions from each package in it’s own graphviz subgraph. The graph for an entire package for all loaded code isn’t really that useful, so I made another function to narrow it down. Here I’m specifying the list of packages to render, and the list of functions to show.

STATIC-ANALYSIS> (->dot (function-call-graph '(:alexandria) '(alexandria:rotate))) digraph g{ subgraph clusterG1109{ label="PACKAGE ALEXANDRIA.0.DEV" G1040 [label="ROTATE-HEAD-TO-TAIL"] G1049 [label="SAFE-ENDP"] G1054 [label="CIRCULAR-LIST-ERROR"] G1051 [label="PROPER-LIST-LENGTH"] G1042 [label="ROTATE-TAIL-TO-HEAD"] G1041 [label="ROTATE"] } G1040 -> G1051 G1051 -> G1049 G1051 -> G1054 G1042 -> G1051 G1041 -> G1040 G1041 -> G1042 } NIL

Some systems have very complicated call graphs. At work we do a lot with clsql, and the overall call graph even from one function can get complicated quick:

So I added a depth param to keep the graph smaller, let’s say 3:

STATIC-ANALYSIS> (->dot (function-call-graph '(:clsql-sys :clsql-sqlite3) '(clsql:map-query) 2))

Anyhoo, a fun toy, and I had a fun time writing it.