Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/lsp/handlers/did_change.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub fn handler(comptime ServerType: type) type {
const version_value = text_doc.get("version") orelse std.json.Value{ .integer = 0 };
const version: i64 = if (std.meta.activeTag(version_value) == .integer)
version_value.integer
else if (std.meta.activeTag(version_value) == .float)
else if (std.meta.activeTag(version_value) == .float and std.math.isFinite(version_value.float) and version_value.float >= @as(f64, @floatFromInt(std.math.minInt(i64))) and version_value.float < @as(f64, @floatFromInt(std.math.maxInt(i64))))
@intFromFloat(version_value.float)
else
0;
Expand All @@ -46,7 +46,7 @@ pub fn handler(comptime ServerType: type) type {
var change = DocumentStore.ContentChange{ .text = text };
if (change_obj.get("range")) |range_value| {
change.range = parseRange(range_value) catch |err| {
std.log.err("invalid range for {s}: {s}", .{ uri, @errorName(err) });
std.log.warn("invalid range for {s}: {s}", .{ uri, @errorName(err) });
return;
};
}
Expand All @@ -66,7 +66,7 @@ pub fn handler(comptime ServerType: type) type {

if (saw_full_change) {
if (parsed_changes.items.len != 1) {
std.log.err("received invalid mix of full and incremental changes for {s}", .{uri});
std.log.warn("received invalid mix of full and incremental changes for {s}", .{uri});
return;
}
try self.doc_store.upsert(uri, version, parsed_changes.items[0].text);
Expand All @@ -77,7 +77,7 @@ pub fn handler(comptime ServerType: type) type {
error.InvalidPosition,
error.InvalidRange,
error.NoChanges,
=> std.log.err("failed to apply incremental change for {s}: {s}", .{ uri, @errorName(err) }),
=> std.log.warn("failed to apply incremental change for {s}: {s}", .{ uri, @errorName(err) }),
};
}

Expand All @@ -104,10 +104,14 @@ pub fn handler(comptime ServerType: type) type {
fn parseIndex(obj: std.json.ObjectMap, field: []const u8) error{ MissingField, InvalidField }!usize {
const value = obj.get(field) orelse return error.MissingField;
if (std.meta.activeTag(value) == .integer) {
return if (value.integer < 0) error.InvalidField else @intCast(value.integer);
return std.math.cast(usize, value.integer) orelse error.InvalidField;
}
if (std.meta.activeTag(value) == .float) {
return if (value.float < 0) error.InvalidField else @intFromFloat(value.float);
const f = value.float;
if (!std.math.isFinite(f) or f < 0 or f >= @as(f64, @floatFromInt(std.math.maxInt(usize)))) {
return error.InvalidField;
}
return @intFromFloat(f);
}
return error.InvalidField;
}
Expand Down
2 changes: 1 addition & 1 deletion src/lsp/handlers/did_open.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn handler(comptime ServerType: type) type {
const version_value = text_doc.get("version") orelse std.json.Value{ .integer = 0 };
const version: i64 = if (std.meta.activeTag(version_value) == .integer)
version_value.integer
else if (std.meta.activeTag(version_value) == .float)
else if (std.meta.activeTag(version_value) == .float and std.math.isFinite(version_value.float) and version_value.float >= @as(f64, @floatFromInt(std.math.minInt(i64))) and version_value.float < @as(f64, @floatFromInt(std.math.maxInt(i64))))
@intFromFloat(version_value.float)
else
0;
Expand Down
57 changes: 57 additions & 0 deletions src/lsp/test/handler_unit_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,63 @@ fn responseWithId(allocator: std.mem.Allocator, responses: [][]u8, wanted_id: i6
return error.MissingResponse;
}

test "didChange handler handles out-of-bounds float values without panicking" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const file_uri = try tempFileUri(allocator, &tmp, "change_float.roc");
defer allocator.free(file_uri);

const change_body = try std.fmt.allocPrint(allocator,
\\{{"jsonrpc":"2.0","method":"textDocument/didChange","params":{{"textDocument":{{"uri":"{s}","version":1e100}},"contentChanges":[{{"text":"x","range":{{"start":{{"line":1e100,"character":0}},"end":{{"line":1,"character":0}}}}}}]}}}}
, .{file_uri});
defer allocator.free(change_body);
const input = try requestInput(allocator, file_uri, "x = 1", change_body);
defer allocator.free(input);

var writer_buffer: [16384]u8 = undefined;
const run = try runUnitServer(allocator, input, &writer_buffer);
defer freeRun(allocator, run);
}

test "didChange handler handles valid float range indices correctly" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const file_uri = try tempFileUri(allocator, &tmp, "change_valid_float.roc");
defer allocator.free(file_uri);

const change_body = try std.fmt.allocPrint(allocator,
\\{{"jsonrpc":"2.0","method":"textDocument/didChange","params":{{"textDocument":{{"uri":"{s}","version":2.0}},"contentChanges":[{{"text":"x","range":{{"start":{{"line":0.0,"character":0.0}},"end":{{"line":0.0,"character":1.0}}}}}}]}}}}
, .{file_uri});
defer allocator.free(change_body);
const input = try requestInput(allocator, file_uri, "x = 1", change_body);
defer allocator.free(input);

var writer_buffer: [16384]u8 = undefined;
const run = try runUnitServer(allocator, input, &writer_buffer);
defer freeRun(allocator, run);
}

test "didOpen handler handles out-of-bounds float version without panicking" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const file_uri = try tempFileUri(allocator, &tmp, "open_float.roc");
defer allocator.free(file_uri);

const open_body = try std.fmt.allocPrint(allocator,
\\{{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{{"textDocument":{{"uri":"{s}","version":1e100,"text":"x = 1"}}}}}}
, .{file_uri});
defer allocator.free(open_body);
const input = try requestInput(allocator, file_uri, "x = 1", open_body);
defer allocator.free(input);

var writer_buffer: [16384]u8 = undefined;
const run = try runUnitServer(allocator, input, &writer_buffer);
defer freeRun(allocator, run);
}

test "formatting handler formats simple expression with test syntax driver" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
Expand Down
Loading