mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
compiler: fix crash if file contents change during update
When reporting a compile error, we would load the new file, but assume we could apply old AST/token indices (etc) to it, potentially causing crashes. Instead, if the file stat has changed since it was loaded, just emit an error that the file was modified mid-update.
This commit is contained in:
parent
4ea4728084
commit
806470b492
2 changed files with 48 additions and 39 deletions
|
|
@ -3935,17 +3935,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {
|
|||
for (zcu.failed_imports.items) |failed| {
|
||||
assert(zcu.alive_files.contains(failed.file_index)); // otherwise it wouldn't have been added
|
||||
const file = zcu.fileByIndex(failed.file_index);
|
||||
const source = file.getSource(zcu) catch |err| {
|
||||
try unableToLoadZcuFile(zcu, &bundle, file, err);
|
||||
continue;
|
||||
};
|
||||
const tree = file.getTree(zcu) catch |err| {
|
||||
try unableToLoadZcuFile(zcu, &bundle, file, err);
|
||||
continue;
|
||||
};
|
||||
const start = tree.tokenStart(failed.import_token);
|
||||
const end = start + tree.tokenSlice(failed.import_token).len;
|
||||
const loc = std.zig.findLineColumn(source.bytes, start);
|
||||
const loc = std.zig.findLineColumn(tree.source, start);
|
||||
try bundle.addRootErrorMessage(.{
|
||||
.msg = switch (failed.kind) {
|
||||
.file_outside_module_root => try bundle.addString("import of file outside module path"),
|
||||
|
|
@ -4338,7 +4334,7 @@ pub fn addModuleErrorMsg(
|
|||
const err_span = err_src_loc.span(zcu) catch |err| {
|
||||
return unableToLoadZcuFile(zcu, eb, err_src_loc.file_scope, err);
|
||||
};
|
||||
const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main);
|
||||
const err_loc = std.zig.findLineColumn(err_source, err_span.main);
|
||||
|
||||
var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .empty;
|
||||
defer ref_traces.deinit(gpa);
|
||||
|
|
@ -4434,7 +4430,7 @@ pub fn addModuleErrorMsg(
|
|||
const span = note_src_loc.span(zcu) catch |err| {
|
||||
return unableToLoadZcuFile(zcu, eb, note_src_loc.file_scope, err);
|
||||
};
|
||||
const loc = std.zig.findLineColumn(source.bytes, span.main);
|
||||
const loc = std.zig.findLineColumn(source, span.main);
|
||||
|
||||
const omit_source_line = loc.eql(err_loc) or (last_note_loc != null and loc.eql(last_note_loc.?));
|
||||
last_note_loc = loc;
|
||||
|
|
@ -4489,7 +4485,7 @@ fn addReferenceTraceFrame(
|
|||
try unableToLoadZcuFile(zcu, eb, src.file_scope, err);
|
||||
return error.AlreadyReported;
|
||||
};
|
||||
const loc = std.zig.findLineColumn(source.bytes, span.main);
|
||||
const loc = std.zig.findLineColumn(source, span.main);
|
||||
try ref_traces.append(gpa, .{
|
||||
.decl_name = try eb.printString("{s}{s}", .{ name, if (inlined) " [inlined]" else "" }),
|
||||
.src_loc = try eb.addSourceLocation(.{
|
||||
|
|
@ -4545,8 +4541,13 @@ pub fn unableToLoadZcuFile(
|
|||
file: *Zcu.File,
|
||||
err: Zcu.File.GetSourceError,
|
||||
) Allocator.Error!void {
|
||||
const msg = switch (err) {
|
||||
error.OutOfMemory => |e| return e,
|
||||
error.FileChanged => try eb.addString("file contents changed during update"),
|
||||
else => |e| try eb.printString("unable to load: {t}", .{e}),
|
||||
};
|
||||
try eb.addRootErrorMessage(.{
|
||||
.msg = try eb.printString("unable to load: {t}", .{err}),
|
||||
.msg = msg,
|
||||
.src_loc = try file.errorBundleWholeFileSrc(zcu, eb),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
68
src/Zcu.zig
68
src/Zcu.zig
|
|
@ -925,8 +925,11 @@ pub const File = struct {
|
|||
/// allocated into `gpa`.
|
||||
path: Compilation.Path,
|
||||
|
||||
/// Populated only when emitting error messages; see `getSource`.
|
||||
source: ?[:0]const u8,
|
||||
/// Populated only when emitting error messages; see `getTree`.
|
||||
tree: ?Ast,
|
||||
|
||||
zir: ?Zir,
|
||||
zoir: ?Zoir,
|
||||
|
||||
|
|
@ -1033,25 +1036,27 @@ pub const File = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub const Source = struct {
|
||||
bytes: [:0]const u8,
|
||||
stat: Cache.File.Stat,
|
||||
};
|
||||
|
||||
pub const GetSourceError = error{
|
||||
OutOfMemory,
|
||||
FileTooBig,
|
||||
Streaming,
|
||||
} || std.fs.File.OpenError || std.fs.File.ReadError;
|
||||
FileChanged,
|
||||
} || std.Io.File.OpenError || std.Io.File.Reader.Error;
|
||||
|
||||
pub fn getSource(file: *File, zcu: *const Zcu) GetSourceError!Source {
|
||||
/// This must only be called in error conditions where `stat` *is* populated. It returns the
|
||||
/// contents of the source file, assuming the stat has not changed since it was originally
|
||||
/// loaded.
|
||||
pub fn getSource(file: *File, zcu: *const Zcu) GetSourceError![:0]const u8 {
|
||||
const gpa = zcu.gpa;
|
||||
const io = zcu.comp.io;
|
||||
|
||||
if (file.source) |source| return .{
|
||||
.bytes = source,
|
||||
.stat = file.stat,
|
||||
};
|
||||
if (file.source) |source| return source;
|
||||
|
||||
switch (file.status) {
|
||||
.never_loaded => unreachable, // stat must be populated
|
||||
.retryable_failure => unreachable, // stat must be populated
|
||||
.astgen_failure, .success => {},
|
||||
}
|
||||
|
||||
assert(file.stat.size <= std.math.maxInt(u32)); // `PerThread.updateFile` checks this
|
||||
|
||||
var f = f: {
|
||||
const dir, const sub_path = file.path.openInfo(zcu.comp.dirs);
|
||||
|
|
@ -1059,40 +1064,43 @@ pub const File = struct {
|
|||
};
|
||||
defer f.close();
|
||||
|
||||
const stat = try f.stat();
|
||||
const stat = f.stat() catch |err| switch (err) {
|
||||
error.Streaming => {
|
||||
// Since `file.stat` is populated, this was previously a file stream; since it is
|
||||
// now not a file stream, it must have changed.
|
||||
return error.FileChanged;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
if (stat.size > std.math.maxInt(u32))
|
||||
return error.FileTooBig;
|
||||
if (stat.inode != file.stat.inode or
|
||||
stat.size != file.stat.size or
|
||||
stat.mtime.nanoseconds != file.stat.mtime.nanoseconds)
|
||||
{
|
||||
return error.FileChanged;
|
||||
}
|
||||
|
||||
const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0);
|
||||
const source = try gpa.allocSentinel(u8, @intCast(file.stat.size), 0);
|
||||
errdefer gpa.free(source);
|
||||
|
||||
var file_reader = f.reader(io, &.{});
|
||||
file_reader.size = stat.size;
|
||||
file_reader.interface.readSliceAll(source) catch return file_reader.err.?;
|
||||
|
||||
// Here we do not modify stat fields because this function is the one
|
||||
// used for error reporting. We need to keep the stat fields stale so that
|
||||
// updateFile can know to regenerate ZIR.
|
||||
|
||||
file.source = source;
|
||||
errdefer comptime unreachable; // don't error after populating `source`
|
||||
|
||||
return .{
|
||||
.bytes = source,
|
||||
.stat = .{
|
||||
.size = stat.size,
|
||||
.inode = stat.inode,
|
||||
.mtime = stat.mtime,
|
||||
},
|
||||
};
|
||||
return source;
|
||||
}
|
||||
|
||||
/// This must only be called in error conditions where `stat` *is* populated. It returns the
|
||||
/// parsed AST of the source file, assuming the stat has not changed since it was originally
|
||||
/// loaded.
|
||||
pub fn getTree(file: *File, zcu: *const Zcu) GetSourceError!*const Ast {
|
||||
if (file.tree) |*tree| return tree;
|
||||
|
||||
const source = try file.getSource(zcu);
|
||||
file.tree = try .parse(zcu.gpa, source.bytes, file.getMode());
|
||||
file.tree = try .parse(zcu.gpa, source, file.getMode());
|
||||
return &file.tree.?;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue