diff --git a/build.zig b/build.zig index c4fea91..52477c7 100644 --- a/build.zig +++ b/build.zig @@ -49,6 +49,18 @@ pub fn build(b: *std.build.Builder) !void { "enable_tracy_callstack", 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_string = b.fmt("{d}.{d}.{d}", .{ zls_version.major, zls_version.minor, zls_version.patch }); diff --git a/src/analysis.zig b/src/analysis.zig index c314930..ee34216 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -2389,30 +2389,6 @@ pub const DocumentScope = struct { error_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 { for (self.scopes.items) |*scope| { scope.deinit(allocator); diff --git a/src/debug.zig b/src/debug.zig new file mode 100644 index 0000000..02a1b38 --- /dev/null +++ b/src/debug.zig @@ -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); + } +}; diff --git a/src/main.zig b/src/main.zig index 6aaab47..29122ec 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,6 +8,7 @@ const configuration = @import("configuration.zig"); const Server = @import("Server.zig"); const setup = @import("setup.zig"); const Header = @import("Header.zig"); +const debug = @import("debug.zig"); const logger = std.log.scoped(.main); @@ -263,9 +264,12 @@ const stack_frames = switch (zig_builtin.mode) { pub fn main() !void { var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){}; 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{ .config = undefined, diff --git a/src/zls.zig b/src/zls.zig index 21b1095..fb362d0 100644 --- a/src/zls.zig +++ b/src/zls.zig @@ -3,6 +3,7 @@ pub const analysis = @import("analysis.zig"); pub const Header = @import("Header.zig"); +pub const debug = @import("debug.zig"); pub const offsets = @import("offsets.zig"); pub const Config = @import("Config.zig"); pub const Server = @import("Server.zig");