CI Hex.pm Docs

Whole-program BEAM analysis for subtle OTP and supervision bugs. The package is panoptes — Argus Panoptes, the hundred-eyed watchman — and its modules are Argus.*.

Argus disassembles compiled .beam files, extracts facts from the bytecode, and evaluates Souffle Datalog rules to detect BEAM/OTP-specific anti-patterns: supervision-tree coupling, GenServer deadlocks, leaked tasks, ETS misuse, atom-table exhaustion, and more.

Installation

def deps do
  [
    {:panoptes, "~> 0.13"}
  ]
end

Souffle must be installed and available on your PATH.

To run the analyses over a project, use scry, the Mix compiler built on this library: it runs them incrementally after every compile and reports findings as compiler diagnostics. This package is the engine — the extraction pipeline, the rules, and the in-VM API below.

Approach

Argus is inspired by Doop (JVM), cclyzer++ (LLVM IR), and Gigahorse (EVM), but takes advantage of the BEAM's register-based instruction set to skip the expensive IR-lifting step those frameworks need. BEAM instructions map to Datalog facts directly, with no intermediate representation.

.beam  disassemble  facts (emitter + extractors)  stage 0 call graph  Souffle rules  findings

The emitter walks every instruction and records the generic facts — instructions, registers, control flow, calls, literals. The extractors read the same bytecode for what the analyses reason about: behaviours, supervision trees, process calls, monitors, ETS, return shapes. A shared call graph is derived once per run, and each analysis is one Souffle program over the facts and a common rule library, producing findings with a severity, a source anchor and a remediation hint.

Analyses

Argus ships 27 BEAM/OTP-specific bug detectors (mix scry --list prints the same table):

AnalysisDetects
atom_safetyatom table exhaustion, unsafe deserialization, and code injection
call_cyclemodule-level synchronous call cycles (deadlocks)
callback_receivereceive inside an OTP callback, which consumes the behaviour's own mailbox
coverageextractor coverage and imprecision (meta-analysis)
deferred_startup_deadlockhandle_continue deadlocks and crash loops
distributedRPC without timeouts, :global races, init blocking on nodes
error_handlingswallowed errors, ignored results, exit misuse
etsETS table ownership, concurrency options, and lifecycle
gen_statemunreachable states and terminal states that never stop
message_contractmessages a module sends itself but cannot handle
monitor_leakmonitors left live after a timed wait gave up
one_for_one_couplingcross-branch coupling under one_for_one supervisors
process_bottlenecksynchronous call fan-in (serialization bottlenecks)
process_registryduplicate names, whereis races, registry collisions
purity@pure contracts checked against the call graph and an effect model
reply_contracthandle_call clauses that defer a reply they cannot send
request_surfacedangerous operations reachable from request-handling callbacks
secret_exposureschema fields holding secrets that inspect/1 will print
shutdown_safetycleanup in terminate/2 that a supervisor shutdown will skip
supervisionsupervision tree structure and anti-patterns
sync_call_in_initsynchronous calls in init/1 (startup deadlocks)
timeout_chainGenServer timeout chains and blocking cast handlers
tls_verificationTLS connections that do not verify the peer
transaction_safetyside effects inside a DB transaction that a rollback cannot undo
unbounded_dynamic_childrenunbounded process creation reachable from a request
unlinked_spawnunlinked (orphan) process spawns
unsafe_taskleaked async tasks and unchecked Task.Supervisor.start_child

Programmatic API

# Every analysis, one extraction: structured findings with severity,
# source anchors and remediation hints.
{:ok, %{findings: findings}} = Argus.run_analyses([MyApp.Supervisor, MyApp.WorkerA])

# One analysis, raw output relations.
{:ok, results} = Argus.analyze([MyApp.Supervisor, MyApp.WorkerA], :supervision)

# Ad-hoc Datalog rules over the same facts.
{:ok, results} = Argus.analyze([MyApp.Worker], {:custom, "path/to/rules.dl"})

License

MIT