Skip to content

JuliaDebug/Infiltrator.jl

master
Switch branches/tags
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
src
 
 
 
 
 
 
 
 
 
 
 
 

Infiltrator.jl CI version

This packages provides a macro called @infiltrate, which sets a "breakpoint" in a local context (similar to Matlab's keyboard function and IPython's embed). The advantage of this macro over e.g. Debugger.jl is that all code is completely compiled, so the performance overhead should be negligible.

Note that you cannot access other functions in the callstack, or step into functions. If you need that functionality, use Debugger.jl, VSCode's or Juno's debugger.

Running code that ends up triggering the @infiltrate REPL mode via inline evaluation in VSCode or Juno can cause issues, so it's recommended to always use the REPL directly.

@infiltrate

@infiltrate cond = true

@infiltrate sets an infiltration point (or breakpoint).

When the infiltration point is hit, it will drop you into an interactive REPL session that lets you inspect local variables and the call stack as well as execute aribtrary statements in the context of the current functions module.

This macro also accepts an optional argument cond that must evaluate to a boolean, and then this macro will serve as a "conditinal breakpoint", which starts inspections only when its condition is true.

You can also use

if isdefined(Main, :Infiltrator)
  Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__)
end

to infiltrate package code without any post-hoc evaluation into the module (because the functional form does not require Infiltrator to be loaded at compiletime).

@exfiltrate

@exfiltrate

Assigns all local variables into global storage.

The safehouse

Exfiltrating variables (with @exfiltrate or by assignment in a @infiltrate session) happens by assigning the variable to a global storage space (backed by a module); any exfiltrated objects can be directly accessed, via Infiltrator.store or its exported aliases safehouse or exfiltrated:

julia> foo(x) = @exfiltrate
foo (generic function with 1 method)

julia> foo(3)

julia> safehouse.x # or exfiltrated.x
3

You can reset the safehouse with Infiltrator.clear_store!().

You can also assign a specific module with Infiltrator.set_store!(mod). This allows you to e.g. set the backing module to Main and therefore export the contents of the safehouse to the global namespace (although doing so is not recommended).

Example usage:

julia> function f(x)
         out = []
         for i in x
           push!(out, 2i)
           @infiltrate
         end
         out
       end
f (generic function with 1 method)

julia> f([1,2,3])
Infiltrating f(x::Vector{Int64})
  at REPL[10]:5

infil> ?
  Code entered is evaluated in the current functions module. Note that you cannot change local
  variables, but can assign to globals in a permanent store module.

  The following commands are special cased:
    - `?`: Print this help text.
    - `@trace`: Print the current stack trace.
    - `@locals`: Print local variables. `@locals x y` only prints `x` and `y`.
    - `@exfiltrate`: Save all local variables into the store. `@exfiltrate x y` saves `x` and `y`;
      this variant can also exfiltrate variables defined in the `infil>` REPL.
    - `@toggle`: Toggle infiltrating at this `@infiltrate` spot (clear all with `Infiltrator.clear_disabled!()`).
    - `@continue`: Continue to the next infiltration point or exit (shortcut: Ctrl-D).
    - `@doc symbol`: Get help for `symbol` (same as in the normal Julia REPL).
    - `@exit`: Stop infiltrating for the remainder of this session and exit (on Julia versions prior to
      1.5 this needs to be manually cleared with `Infiltrator.end_session!()`).

infil> @locals
- out::Vector{Any} = Any[2]
- i::Int64 = 1
- x::Vector{Int64} = [1, 2, 3]

infil> 0//0
ERROR: ArgumentError: invalid rational: zero(Int64)//zero(Int64)
Stacktrace:
 [1] __throw_rational_argerror_zero(T::Type)
   @ Base ./rational.jl:32
 [2] Rational{Int64}(num::Int64, den::Int64)
   @ Base ./rational.jl:34
 [3] Rational
   @ ./rational.jl:39 [inlined]
 [4] //(n::Int64, d::Int64)
   @ Base ./rational.jl:62
 [5] top-level scope
   @ none:1

infil> @toggle
Disabled infiltration at this infiltration point.

infil> @toggle
Enabled infiltration at this infiltration point.

infil> @continue

Infiltrating f(x::Vector{Int64})
  at REPL[10]:5

infil> intermediate = copy(out)
2-element Vector{Any}:
 2
 4

infil> @exfiltrate intermediate x
Exfiltrating 2 local variables into the safehouse.

infil> @exit

3-element Vector{Any}:
 2
 4
 6

julia> safehouse.intermediate
2-element Vector{Any}:
 2
 4

julia> @withstore begin
         x = 23
         x .* intermediate
       end
2-element Vector{Int64}:
 46
 92

Related projects