mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +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| {
|
for (zcu.failed_imports.items) |failed| {
|
||||||
assert(zcu.alive_files.contains(failed.file_index)); // otherwise it wouldn't have been added
|
assert(zcu.alive_files.contains(failed.file_index)); // otherwise it wouldn't have been added
|
||||||
const file = zcu.fileByIndex(failed.file_index);
|
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| {
|
const tree = file.getTree(zcu) catch |err| {
|
||||||
try unableToLoadZcuFile(zcu, &bundle, file, err);
|
try unableToLoadZcuFile(zcu, &bundle, file, err);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
const start = tree.tokenStart(failed.import_token);
|
const start = tree.tokenStart(failed.import_token);
|
||||||
const end = start + tree.tokenSlice(failed.import_token).len;
|
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(.{
|
try bundle.addRootErrorMessage(.{
|
||||||
.msg = switch (failed.kind) {
|
.msg = switch (failed.kind) {
|
||||||
.file_outside_module_root => try bundle.addString("import of file outside module path"),
|
.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| {
|
const err_span = err_src_loc.span(zcu) catch |err| {
|
||||||
return unableToLoadZcuFile(zcu, eb, err_src_loc.file_scope, 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;
|
var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .empty;
|
||||||
defer ref_traces.deinit(gpa);
|
defer ref_traces.deinit(gpa);
|
||||||
|
|
@ -4434,7 +4430,7 @@ pub fn addModuleErrorMsg(
|
||||||
const span = note_src_loc.span(zcu) catch |err| {
|
const span = note_src_loc.span(zcu) catch |err| {
|
||||||
return unableToLoadZcuFile(zcu, eb, note_src_loc.file_scope, 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.?));
|
const omit_source_line = loc.eql(err_loc) or (last_note_loc != null and loc.eql(last_note_loc.?));
|
||||||
last_note_loc = loc;
|
last_note_loc = loc;
|
||||||
|
|
@ -4489,7 +4485,7 @@ fn addReferenceTraceFrame(
|
||||||
try unableToLoadZcuFile(zcu, eb, src.file_scope, err);
|
try unableToLoadZcuFile(zcu, eb, src.file_scope, err);
|
||||||
return error.AlreadyReported;
|
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, .{
|
try ref_traces.append(gpa, .{
|
||||||
.decl_name = try eb.printString("{s}{s}", .{ name, if (inlined) " [inlined]" else "" }),
|
.decl_name = try eb.printString("{s}{s}", .{ name, if (inlined) " [inlined]" else "" }),
|
||||||
.src_loc = try eb.addSourceLocation(.{
|
.src_loc = try eb.addSourceLocation(.{
|
||||||
|
|
@ -4545,8 +4541,13 @@ pub fn unableToLoadZcuFile(
|
||||||
file: *Zcu.File,
|
file: *Zcu.File,
|
||||||
err: Zcu.File.GetSourceError,
|
err: Zcu.File.GetSourceError,
|
||||||
) Allocator.Error!void {
|
) 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(.{
|
try eb.addRootErrorMessage(.{
|
||||||
.msg = try eb.printString("unable to load: {t}", .{err}),
|
.msg = msg,
|
||||||
.src_loc = try file.errorBundleWholeFileSrc(zcu, eb),
|
.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`.
|
/// allocated into `gpa`.
|
||||||
path: Compilation.Path,
|
path: Compilation.Path,
|
||||||
|
|
||||||
|
/// Populated only when emitting error messages; see `getSource`.
|
||||||
source: ?[:0]const u8,
|
source: ?[:0]const u8,
|
||||||
|
/// Populated only when emitting error messages; see `getTree`.
|
||||||
tree: ?Ast,
|
tree: ?Ast,
|
||||||
|
|
||||||
zir: ?Zir,
|
zir: ?Zir,
|
||||||
zoir: ?Zoir,
|
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{
|
pub const GetSourceError = error{
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
FileTooBig,
|
FileChanged,
|
||||||
Streaming,
|
} || std.Io.File.OpenError || std.Io.File.Reader.Error;
|
||||||
} || std.fs.File.OpenError || std.fs.File.ReadError;
|
|
||||||
|
|
||||||
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 gpa = zcu.gpa;
|
||||||
const io = zcu.comp.io;
|
const io = zcu.comp.io;
|
||||||
|
|
||||||
if (file.source) |source| return .{
|
if (file.source) |source| return source;
|
||||||
.bytes = source,
|
|
||||||
.stat = file.stat,
|
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: {
|
var f = f: {
|
||||||
const dir, const sub_path = file.path.openInfo(zcu.comp.dirs);
|
const dir, const sub_path = file.path.openInfo(zcu.comp.dirs);
|
||||||
|
|
@ -1059,40 +1064,43 @@ pub const File = struct {
|
||||||
};
|
};
|
||||||
defer f.close();
|
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))
|
if (stat.inode != file.stat.inode or
|
||||||
return error.FileTooBig;
|
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);
|
errdefer gpa.free(source);
|
||||||
|
|
||||||
var file_reader = f.reader(io, &.{});
|
var file_reader = f.reader(io, &.{});
|
||||||
file_reader.size = stat.size;
|
file_reader.size = stat.size;
|
||||||
file_reader.interface.readSliceAll(source) catch return file_reader.err.?;
|
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;
|
file.source = source;
|
||||||
errdefer comptime unreachable; // don't error after populating `source`
|
errdefer comptime unreachable; // don't error after populating `source`
|
||||||
|
|
||||||
return .{
|
return source;
|
||||||
.bytes = source,
|
|
||||||
.stat = .{
|
|
||||||
.size = stat.size,
|
|
||||||
.inode = stat.inode,
|
|
||||||
.mtime = stat.mtime,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
pub fn getTree(file: *File, zcu: *const Zcu) GetSourceError!*const Ast {
|
||||||
if (file.tree) |*tree| return tree;
|
if (file.tree) |*tree| return tree;
|
||||||
|
|
||||||
const source = try file.getSource(zcu);
|
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.?;
|
return &file.tree.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue