Debugging utilities (#860)

* add debug printing for Ast and DocumentScope

* add optional failing allocator
This commit is contained in:
Techatrix 2022-12-30 23:42:53 +00:00 committed by GitHub
parent f473088b64
commit 94ec3a0a86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 155 additions and 26 deletions

View File

@ -49,6 +49,18 @@ pub fn build(b: *std.build.Builder) !void {
"enable_tracy_callstack", "enable_tracy_callstack",
b.option(bool, "enable_tracy_callstack", "Enable callstack graphs.") orelse false, b.option(bool, "enable_tracy_callstack", "Enable callstack graphs.") orelse false,
); );
exe_options.addOption(
bool,
"enable_failing_allocator",
b.option(bool, "enable_failing_allocator", "Whether to use a randomly failing allocator.") orelse false,
);
exe_options.addOption(
u32,
"enable_failing_allocator_likelihood",
b.option(u32, "enable_failing_allocator_likelihood", "The chance that an allocation will fail is `1/likelihood`") orelse 256,
);
const version = v: { const version = v: {
const version_string = b.fmt("{d}.{d}.{d}", .{ zls_version.major, zls_version.minor, zls_version.patch }); const version_string = b.fmt("{d}.{d}.{d}", .{ zls_version.major, zls_version.minor, zls_version.patch });

View File

@ -2389,30 +2389,6 @@ pub const DocumentScope = struct {
error_completions: CompletionSet, error_completions: CompletionSet,
enum_completions: CompletionSet, enum_completions: CompletionSet,
pub fn debugPrint(self: DocumentScope) void {
for (self.scopes.items) |scope| {
log.debug(
\\--------------------------
\\Scope {}, loc: [{d}, {d})
\\ {d} usingnamespaces
\\Decls:
, .{
scope.data,
scope.loc.start,
scope.loc.end,
scope.uses.len,
});
var decl_it = scope.decls.iterator();
var idx: usize = 0;
while (decl_it.next()) |_| : (idx += 1) {
if (idx != 0) log.debug(", ", .{});
}
// log.debug("{s}", .{name_decl.key});
log.debug("\n--------------------------\n", .{});
}
}
pub fn deinit(self: *DocumentScope, allocator: std.mem.Allocator) void { pub fn deinit(self: *DocumentScope, allocator: std.mem.Allocator) void {
for (self.scopes.items) |*scope| { for (self.scopes.items) |*scope| {
scope.deinit(allocator); scope.deinit(allocator);

136
src/debug.zig Normal file
View File

@ -0,0 +1,136 @@
const std = @import("std");
const analysis = @import("analysis.zig");
const offsets = @import("offsets.zig");
pub fn printTree(tree: std.zig.Ast) void {
if (!std.debug.runtime_safety) @compileError("this function should only be used in debug mode!");
std.debug.print(
\\
\\nodes tag lhs rhs token
\\-----------------------------------------------
\\
, .{});
var i: usize = 0;
while (i < tree.nodes.len) : (i += 1) {
std.debug.print(" {d:<3} {s:<20} {d:<3} {d:<3} {d:<3} {s}\n", .{
i,
@tagName(tree.nodes.items(.tag)[i]),
tree.nodes.items(.data)[i].lhs,
tree.nodes.items(.data)[i].rhs,
tree.nodes.items(.main_token)[i],
offsets.tokenToSlice(tree, tree.nodes.items(.main_token)[i]),
});
}
std.debug.print(
\\
\\tokens tag start
\\----------------------------------
\\
, .{});
i = 0;
while (i < tree.tokens.len) : (i += 1) {
std.debug.print(" {d:<3} {s:<20} {d:<}\n", .{
i,
@tagName(tree.tokens.items(.tag)[i]),
tree.tokens.items(.start)[i],
});
}
}
pub fn printDocumentScope(doc_scope: analysis.DocumentScope) void {
if (!std.debug.runtime_safety) @compileError("this function should only be used in debug mode!");
for (doc_scope.scopes.items) |scope, i| {
if (i != 0) std.debug.print("\n\n", .{});
std.debug.print(
\\[{d}, {d}] {}
\\usingnamespaces: {d}
\\Decls:
\\
, .{
scope.loc.start,
scope.loc.end,
scope.data,
scope.uses.items.len,
});
var decl_it = scope.decls.iterator();
var idx: usize = 0;
while (decl_it.next()) |entry| : (idx += 1) {
std.debug.print(" {s:<8} {}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
}
}
}
pub const FailingAllocator = struct {
internal_allocator: std.mem.Allocator,
random: std.rand.DefaultPrng,
likelihood: u32,
/// the chance that an allocation will fail is `1/likelihood`
/// `likelihood == 0` means that every allocation will fail
/// `likelihood == std.math.intMax(u32)` means that no allocation will be forced to fail
pub fn init(internal_allocator: std.mem.Allocator, likelihood: u32) FailingAllocator {
var seed = std.mem.zeroes([8]u8);
std.os.getrandom(&seed) catch {};
return FailingAllocator{
.internal_allocator = internal_allocator,
.random = std.rand.DefaultPrng.init(@bitCast(u64, seed)),
.likelihood = likelihood,
};
}
pub fn allocator(self: *FailingAllocator) std.mem.Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.free = free,
},
};
}
fn alloc(
ctx: *anyopaque,
len: usize,
log2_ptr_align: u8,
return_address: usize,
) ?[*]u8 {
const self = @ptrCast(*FailingAllocator, @alignCast(@alignOf(FailingAllocator), ctx));
if (shouldFail(self)) return null;
return self.internal_allocator.rawAlloc(len, log2_ptr_align, return_address);
}
fn resize(
ctx: *anyopaque,
old_mem: []u8,
log2_old_align: u8,
new_len: usize,
ra: usize,
) bool {
const self = @ptrCast(*FailingAllocator, @alignCast(@alignOf(FailingAllocator), ctx));
if (!self.internal_allocator.rawResize(old_mem, log2_old_align, new_len, ra))
return false;
return true;
}
fn free(
ctx: *anyopaque,
old_mem: []u8,
log2_old_align: u8,
ra: usize,
) void {
const self = @ptrCast(*FailingAllocator, @alignCast(@alignOf(FailingAllocator), ctx));
self.internal_allocator.rawFree(old_mem, log2_old_align, ra);
}
fn shouldFail(self: *FailingAllocator) bool {
if (self.likelihood == std.math.maxInt(u32)) return false;
return 0 == self.random.random().intRangeAtMostBiased(u32, 0, self.likelihood);
}
};

View File

@ -8,6 +8,7 @@ const configuration = @import("configuration.zig");
const Server = @import("Server.zig"); const Server = @import("Server.zig");
const setup = @import("setup.zig"); const setup = @import("setup.zig");
const Header = @import("Header.zig"); const Header = @import("Header.zig");
const debug = @import("debug.zig");
const logger = std.log.scoped(.main); const logger = std.log.scoped(.main);
@ -263,9 +264,12 @@ const stack_frames = switch (zig_builtin.mode) {
pub fn main() !void { pub fn main() !void {
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){}; var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){};
defer _ = gpa_state.deinit(); defer _ = gpa_state.deinit();
var tracy_state = if (tracy.enable_allocation) tracy.tracyAllocator(gpa_state.allocator()) else void{};
const allocator: std.mem.Allocator = if (tracy.enable_allocation) tracy_state.allocator() else gpa_state.allocator(); var tracy_state = if (tracy.enable_allocation) tracy.tracyAllocator(gpa_state.allocator()) else void{};
const inner_allocator: std.mem.Allocator = if (tracy.enable_allocation) tracy_state.allocator() else gpa_state.allocator();
var failing_allocator_state = if(build_options.enable_failing_allocator) debug.FailingAllocator.init(inner_allocator, build_options.enable_failing_allocator_likelihood) else void{};
const allocator: std.mem.Allocator = if(build_options.enable_failing_allocator) failing_allocator_state.allocator() else inner_allocator;
var config = ConfigWithPath{ var config = ConfigWithPath{
.config = undefined, .config = undefined,

View File

@ -3,6 +3,7 @@
pub const analysis = @import("analysis.zig"); pub const analysis = @import("analysis.zig");
pub const Header = @import("Header.zig"); pub const Header = @import("Header.zig");
pub const debug = @import("debug.zig");
pub const offsets = @import("offsets.zig"); pub const offsets = @import("offsets.zig");
pub const Config = @import("Config.zig"); pub const Config = @import("Config.zig");
pub const Server = @import("Server.zig"); pub const Server = @import("Server.zig");