SHA256
3
9
Fork
You've already forked lens
0
Zig debugging and memory analysis library
  • Zig 100%
Find a file
2026年05月15日 02:46:21 +03:00
screenshots fixed sentinel terminated segments not including the final sentinel element 2026年05月14日 13:43:12 +03:00
src updated to 0.17.0-dev.304+9787df942 2026年05月15日 02:46:21 +03:00
.gitignore All tests pass 2026年03月17日 21:55:42 +02:00
build.zig updated to 0.17.0-dev.304+9787df942 2026年05月15日 02:46:21 +03:00
build.zig.zon removed core_utils dependency 2026年05月02日 17:40:08 +03:00
LICENSE removed core_utils dependency 2026年05月02日 17:40:08 +03:00
README.md updated to 0.17.0-dev.304+9787df942 2026年05月15日 02:46:21 +03:00

Lens

Zig debugging library with a focus on memory forensics, data inspection and developer ergonomics.

Currently Linux only

Data formatters

Formatters give you instant, frictionless visibility into the state of your algorithms and data structures. They are designed to completely bypass the setup overhead of a traditional debugger, giving you powerful structural insights with a single drop-in function call.

constlens=@import("lens");constdebug=lens.debug;{// The one-parameter functions use the default config and thread-safe allocatorsdebug.inspectMemory(self);debug.prettyPrint(self)debug.prettyPrint(.{// any type of input is accepted.i=i,.current_state=self,.some_var=some_var,});}

All input is assumed to be potentially corrupt and strictly validates memory before reading. The goal is to never crash, and to succesfully format all types of data via comptime reflection.

Pretty printing

Formats any Zig structure into a human readable graphic. Some highlights include:

  1. Dynamically decide whether to use newline or comma separation of elements
  2. Iterator detection
  3. Terminal colorscheme respecting ANSI highlighting
  4. String heuristics, utf8 and unprintable character escaping
  5. Fault tagging; notifies when a memory read was not performed, e.g. would cause a segfault, tag is broken...
  6. Highly configurable; default is to highly filter noise

The goal is so that you never have to write another custom formatter for a datastructure. Instead let the default behaviour do its work, or define a custom iterator for it if its a highly optimized object. See pretty_print.zig for its configuration.

iterators
See above with iterators off
state machine
code

Memory analysis

Structurally visualizes all memory clusters found in the input. The top cluster, e.g. the value passed-by-copy to the function, is only used for memory-object collection purposes. Some key features:

  1. Explains the explicit reason why a pointer is broken
  2. Notifies on semantic problems: underlying memory is zig-undefined, tag is broken, misalignment...
  3. Perfectly segmentizes composite types: optionals, error unions, tagged unions, structs. This segmentation perfectly mirrors how the Zig compiler segmentizes such types. Homogeneous sequences are not segmented to avoid spam (slices, arrays)
  4. Visualizes memory aliasing and adjacency
  5. Prints relevant OS information for the found memory segments

See mem_inspect.zig for configuration.

Example with two game entities that target each other

constGameEntity=struct{id:u64,title:?[]constu8,stats:?PlayerStats,magic_state:anyerror!void,inventory:[3]InventoryItem,grid_pos:[2][2]?u8,target:?*constSelf,faction_id:u8align(32),constSelf=@This();};

code
pretty printed input
mem inspect output

Zig EnumMap

code
memory inspect output

Broken memory examples

code
memory inspect output memory inspect output

  • The memory validity tags are assigned in a strict precedence order. See analysis.zig for more info on the tags.
  • The top level names in the graphic signify memory regions that were found through pointer-objects.
  • The tree graphic underneath it is its segmentation.
  • The cyan-colored regions are regions that contain aliasing.
  • A mode of r--p/rw- signifies that proc/maps reported r--p while ELF reported rw-

Sentinel scanning

memory inspect output

Sentinels are searched across contiguous memory regions

Known crash conditions

Multithreaded race conditions

The formatters parse proc/maps for determining truth on whether a pointer is pointing to a legal region. This is subject to a race condition where another thread frees memory the formatting thread believes to be valid which then causes a segfault

Testing

Deep equality

trylens.testing.expectDeeplyEqual(expected,actual);

Similar to Zig's std.testing.expectEqualDeep, it's a function for comparing two instances according to the structural semantics defined in src/mem/deeply_equal/equal.zig.

The main purpose is to provide a more ergonomic way to compare sequences with each other.

This should not be used as a general comparison algorithm. Usually the shape of the data is as important as the data itself. This function purely ignores the shape and strict Zig compiler type safety of the data.

Asymmetric unwrapping

All unwrappable types are unwrapped asymmetrically. This includes optionals, unions, error unions, one pointers.

constexpected=5;constmaybe_union:??MyUnion=MyUnion{.value=5};varptr:??*const??MyUnion=&maybe_union;trylens.testing.expectDeeplyEqual(expected,&&&ptr);// passes

expected could also be an arbitrarily wrapped type

Sequences

All of the following are interpreted as a Sequence polymorphism: Tuples, iterators, slices, arrays, vectors and sentinel terminated many pointers.

This makes it extremely ergonomic to test iterators. Consider some complicated algorithm that returns an iterator with a very complicated return type

constResult=union(enum){obj:SomeObject,value:usize,name:[]constu8,maybe_thing:?Something,};constError=error{a,b,c}||Allocator.Error;constIterationResult=Error!Result;

In order to perform a structural expectation check, all you do is:

varit=my_complicated_algorithm.iterator();trylens.testing.expectDeeplyEqual(.{5,2,3,error.a,"mark",null,// @typeOf(null) matches any optional with the null tagerror.c,// error literals not belonging to the same set worksnull,"jack",2,},&it);// passes

The structure of the data is completely ignored. This means that if you are comparing a type such as:

constIterationResult=Error!?Result;

A null Result and a null maybe_thing field will be interpreted as equal. Potentially causing false positives

Usage

Soft dependency

The library is meant to be used during development and then deleted for release

See render_target.zig for configuration fields

// in build.zigconstlens_dep=b.dependency("lens",.{.target=target,.optimize=optimize,.render_ansi=false,// disable color});exe.root_module.addImport("lens",lens_dep.module("lens"));// in build.zig.zon.dependencies=.{.lens=.{.url="https://codeberg.org/jetill/lens/archive/v0.1.4.tar.gz",},},

It can also be enabled as a testing-only soft dependency or via a dev flag

// -Ddev=true constis_dev=b.option(bool,"dev","Enable dev dependencies (lens) inside the core module")orelsefalse;// Always injected to tests// Only injected to main if -Ddev=trueif(b.lazyDependency("lens",.{.target=target,.optimize=optimize,}))|lens_dep|{constlens_mod=lens_dep.module("lens");all_tests.root_module.addImport("lens",lens_mod);if(is_dev){pzre_mod.addImport("lens",lens_mod);}}

Debug no-op

The debug interface can also be turned into no-op automatically in non-debug mode.

constlens_dep=b.dependency("lens",.{.target=target,.optimize=optimize,.debug_only=true,});