make diff.edits memory safe (#1026)

This commit is contained in:
Techatrix 2023-02-27 22:53:16 +00:00 committed by GitHub
parent ed908a2511
commit 89ab9fdf70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 24 deletions

View File

@ -7,7 +7,7 @@ const dmp = DiffMatchPatch{
.diff_timeout = 250, .diff_timeout = 250,
}; };
pub const Error = error{ OutOfMemory, InvalidRange, UnknownError }; pub const Error = error{OutOfMemory};
pub fn edits( pub fn edits(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -15,8 +15,25 @@ pub fn edits(
after: []const u8, after: []const u8,
encoding: offsets.Encoding, encoding: offsets.Encoding,
) Error!std.ArrayListUnmanaged(types.TextEdit) { ) Error!std.ArrayListUnmanaged(types.TextEdit) {
var diffs = try dmp.diff(allocator, before, after, true); var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
var diffs = try dmp.diff(arena.allocator(), before, after, true);
var edit_count: usize = 0;
for (diffs.items) |diff| {
switch (diff.operation) {
.delete => edit_count += 1,
.equal => continue,
.insert => edit_count += 1,
}
}
var eds = std.ArrayListUnmanaged(types.TextEdit){}; var eds = std.ArrayListUnmanaged(types.TextEdit){};
try eds.ensureTotalCapacity(allocator, edit_count);
errdefer {
for (eds.items) |edit| allocator.free(edit.newText);
eds.deinit(allocator);
}
var offset: usize = 0; var offset: usize = 0;
for (diffs.items) |diff| { for (diffs.items) |diff| {
@ -24,13 +41,19 @@ pub fn edits(
switch (diff.operation) { switch (diff.operation) {
.delete => { .delete => {
offset += diff.text.len; offset += diff.text.len;
try eds.append(allocator, .{ .range = offsets.locToRange(before, .{ .start = start, .end = offset }, encoding), .newText = "" }); eds.appendAssumeCapacity(.{
.range = offsets.locToRange(before, .{ .start = start, .end = offset }, encoding),
.newText = "",
});
}, },
.equal => { .equal => {
offset += diff.text.len; offset += diff.text.len;
}, },
.insert => { .insert => {
try eds.append(allocator, .{ .range = offsets.locToRange(before, .{ .start = start, .end = start }, encoding), .newText = diff.text }); eds.appendAssumeCapacity(.{
.range = offsets.locToRange(before, .{ .start = start, .end = start }, encoding),
.newText = try allocator.dupe(u8, diff.text),
});
}, },
} }
} }
@ -95,6 +118,7 @@ pub fn applyTextEdits(
std.sort.sort(types.TextEdit, text_edits_sortable, {}, textEditLessThan); std.sort.sort(types.TextEdit, text_edits_sortable, {}, textEditLessThan);
var final_text = std.ArrayListUnmanaged(u8){}; var final_text = std.ArrayListUnmanaged(u8){};
errdefer final_text.deinit(allocator);
var last: usize = 0; var last: usize = 0;
for (text_edits_sortable) |te| { for (text_edits_sortable) |te| {

View File

@ -1,30 +1,37 @@
const std = @import("std"); const std = @import("std");
const zls = @import("zls"); const zls = @import("zls");
const allocator = std.testing.allocator;
fn gen(alloc: std.mem.Allocator, rand: std.rand.Random) ![]const u8 { fn gen(alloc: std.mem.Allocator, rand: std.rand.Random) ![]const u8 {
var buffer = try alloc.alloc(u8, rand.intRangeAtMost(usize, 16, 1024)); var buffer = try alloc.alloc(u8, rand.intRangeAtMost(usize, 0, 256));
for (buffer) |*b| b.* = rand.intRangeAtMost(u8, 32, 126); for (buffer) |*b| b.* = rand.intRangeAtMost(u8, ' ', '~');
return buffer; return buffer;
} }
test "diff - random" { test "diff - random" {
var arena = std.heap.ArenaAllocator.init(allocator); const allocator = std.testing.allocator;
defer arena.deinit(); try std.testing.checkAllAllocationFailures(allocator, testDiff, .{ 0, .@"utf-8" });
for (0..30) |i| {
var rand = std.rand.DefaultPrng.init(0); try testDiff(allocator, i, .@"utf-8");
try testDiff(allocator, i, .@"utf-16");
var index: usize = 0; try testDiff(allocator, i, .@"utf-32");
while (index < 100) : (index += 1) {
defer _ = arena.reset(.retain_capacity);
const pre = try gen(arena.allocator(), rand.random());
const post = try gen(arena.allocator(), rand.random());
var edits = try zls.diff.edits(arena.allocator(), pre, post, .@"utf-8");
const applied = try zls.diff.applyTextEdits(arena.allocator(), pre, edits.items, .@"utf-8");
try std.testing.expectEqualStrings(post, applied);
} }
} }
fn testDiff(allocator: std.mem.Allocator, seed: u64, encoding: zls.offsets.Encoding) !void {
var rand = std.rand.DefaultPrng.init(seed);
const before = try gen(allocator, rand.random());
defer allocator.free(before);
const after = try gen(allocator, rand.random());
defer allocator.free(after);
var edits = try zls.diff.edits(allocator, before, after, encoding);
defer {
for (edits.items) |edit| allocator.free(edit.newText);
edits.deinit(allocator);
}
const applied = try zls.diff.applyTextEdits(allocator, before, edits.items, encoding);
defer allocator.free(applied);
try std.testing.expectEqualStrings(after, applied);
}