| author | |
| committer | |
| log | 806470b492f15c4e3350ce4a48e607f2c8e94ff8 |
| tree | 3bbc7184dc62cc4f1e583c9e907620517f44e4c7 |
| parent | 4ea472808437c932f6240c0df5e4d5912d282052 |
| signature |
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.2 files changed, 48 insertions(+), 39 deletions(-)
src/Compilation.zig+10-9| ... | ... | @@ -3935,17 +3935,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { |
| 3935 | 3935 | for (zcu.failed_imports.items) |failed| { |
| 3936 | 3936 | assert(zcu.alive_files.contains(failed.file_index)); // otherwise it wouldn't have been added |
| 3937 | 3937 | const file = zcu.fileByIndex(failed.file_index); |
| 3938 | const source = file.getSource(zcu) catch |err| { | |
| 3939 | try unableToLoadZcuFile(zcu, &bundle, file, err); | |
| 3940 | continue; | |
| 3941 | }; | |
| 3942 | 3938 | const tree = file.getTree(zcu) catch |err| { |
| 3943 | 3939 | try unableToLoadZcuFile(zcu, &bundle, file, err); |
| 3944 | 3940 | continue; |
| 3945 | 3941 | }; |
| 3946 | 3942 | const start = tree.tokenStart(failed.import_token); |
| 3947 | 3943 | const end = start + tree.tokenSlice(failed.import_token).len; |
| 3948 | const loc = std.zig.findLineColumn(source.bytes, start); | |
| 3944 | const loc = std.zig.findLineColumn(tree.source, start); | |
| 3949 | 3945 | try bundle.addRootErrorMessage(.{ |
| 3950 | 3946 | .msg = switch (failed.kind) { |
| 3951 | 3947 | .file_outside_module_root => try bundle.addString("import of file outside module path"), |
| ... | ... | @@ -4338,7 +4334,7 @@ pub fn addModuleErrorMsg( |
| 4338 | 4334 | const err_span = err_src_loc.span(zcu) catch |err| { |
| 4339 | 4335 | return unableToLoadZcuFile(zcu, eb, err_src_loc.file_scope, err); |
| 4340 | 4336 | }; |
| 4341 | const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main); | |
| 4337 | const err_loc = std.zig.findLineColumn(err_source, err_span.main); | |
| 4342 | 4338 | |
| 4343 | 4339 | var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .empty; |
| 4344 | 4340 | defer ref_traces.deinit(gpa); |
| ... | ... | @@ -4434,7 +4430,7 @@ pub fn addModuleErrorMsg( |
| 4434 | 4430 | const span = note_src_loc.span(zcu) catch |err| { |
| 4435 | 4431 | return unableToLoadZcuFile(zcu, eb, note_src_loc.file_scope, err); |
| 4436 | 4432 | }; |
| 4437 | const loc = std.zig.findLineColumn(source.bytes, span.main); | |
| 4433 | const loc = std.zig.findLineColumn(source, span.main); | |
| 4438 | 4434 | |
| 4439 | 4435 | const omit_source_line = loc.eql(err_loc) or (last_note_loc != null and loc.eql(last_note_loc.?)); |
| 4440 | 4436 | last_note_loc = loc; |
| ... | ... | @@ -4489,7 +4485,7 @@ fn addReferenceTraceFrame( |
| 4489 | 4485 | try unableToLoadZcuFile(zcu, eb, src.file_scope, err); |
| 4490 | 4486 | return error.AlreadyReported; |
| 4491 | 4487 | }; |
| 4492 | const loc = std.zig.findLineColumn(source.bytes, span.main); | |
| 4488 | const loc = std.zig.findLineColumn(source, span.main); | |
| 4493 | 4489 | try ref_traces.append(gpa, .{ |
| 4494 | 4490 | .decl_name = try eb.printString("{s}{s}", .{ name, if (inlined) " [inlined]" else "" }), |
| 4495 | 4491 | .src_loc = try eb.addSourceLocation(.{ |
| ... | ... | @@ -4545,8 +4541,13 @@ pub fn unableToLoadZcuFile( |
| 4545 | 4541 | file: *Zcu.File, |
| 4546 | 4542 | err: Zcu.File.GetSourceError, |
| 4547 | 4543 | ) Allocator.Error!void { |
| 4544 | const msg = switch (err) { | |
| 4545 | error.OutOfMemory => |e| return e, | |
| 4546 | error.FileChanged => try eb.addString("file contents changed during update"), | |
| 4547 | else => |e| try eb.printString("unable to load: {t}", .{e}), | |
| 4548 | }; | |
| 4548 | 4549 | try eb.addRootErrorMessage(.{ |
| 4549 | .msg = try eb.printString("unable to load: {t}", .{err}), | |
| 4550 | .msg = msg, | |
| 4550 | 4551 | .src_loc = try file.errorBundleWholeFileSrc(zcu, eb), |
| 4551 | 4552 | }); |
| 4552 | 4553 | } |
src/Zcu.zig+38-30| ... | ... | @@ -925,8 +925,11 @@ pub const File = struct { |
| 925 | 925 | /// allocated into `gpa`. |
| 926 | 926 | path: Compilation.Path, |
| 927 | 927 | |
| 928 | /// Populated only when emitting error messages; see `getSource`. | |
| 928 | 929 | source: ?[:0]const u8, |
| 930 | /// Populated only when emitting error messages; see `getTree`. | |
| 929 | 931 | tree: ?Ast, |
| 932 | ||
| 930 | 933 | zir: ?Zir, |
| 931 | 934 | zoir: ?Zoir, |
| 932 | 935 | |
| ... | ... | @@ -1033,25 +1036,27 @@ pub const File = struct { |
| 1033 | 1036 | } |
| 1034 | 1037 | } |
| 1035 | 1038 | |
| 1036 | pub const Source = struct { | |
| 1037 | bytes: [:0]const u8, | |
| 1038 | stat: Cache.File.Stat, | |
| 1039 | }; | |
| 1040 | ||
| 1041 | 1039 | pub const GetSourceError = error{ |
| 1042 | 1040 | OutOfMemory, |
| 1043 | FileTooBig, | |
| 1044 | Streaming, | |
| 1045 | } || std.fs.File.OpenError || std.fs.File.ReadError; | |
| 1041 | FileChanged, | |
| 1042 | } || std.Io.File.OpenError || std.Io.File.Reader.Error; | |
| 1046 | 1043 | |
| 1047 | pub fn getSource(file: *File, zcu: *const Zcu) GetSourceError!Source { | |
| 1044 | /// This must only be called in error conditions where `stat` *is* populated. It returns the | |
| 1045 | /// contents of the source file, assuming the stat has not changed since it was originally | |
| 1046 | /// loaded. | |
| 1047 | pub fn getSource(file: *File, zcu: *const Zcu) GetSourceError![:0]const u8 { | |
| 1048 | 1048 | const gpa = zcu.gpa; |
| 1049 | 1049 | const io = zcu.comp.io; |
| 1050 | 1050 | |
| 1051 | if (file.source) |source| return .{ | |
| 1052 | .bytes = source, | |
| 1053 | .stat = file.stat, | |
| 1054 | }; | |
| 1051 | if (file.source) |source| return source; | |
| 1052 | ||
| 1053 | switch (file.status) { | |
| 1054 | .never_loaded => unreachable, // stat must be populated | |
| 1055 | .retryable_failure => unreachable, // stat must be populated | |
| 1056 | .astgen_failure, .success => {}, | |
| 1057 | } | |
| 1058 | ||
| 1059 | assert(file.stat.size <= std.math.maxInt(u32)); // `PerThread.updateFile` checks this | |
| 1055 | 1060 | |
| 1056 | 1061 | var f = f: { |
| 1057 | 1062 | const dir, const sub_path = file.path.openInfo(zcu.comp.dirs); |
| ... | ... | @@ -1059,40 +1064,43 @@ pub const File = struct { |
| 1059 | 1064 | }; |
| 1060 | 1065 | defer f.close(); |
| 1061 | 1066 | |
| 1062 | const stat = try f.stat(); | |
| 1067 | const stat = f.stat() catch |err| switch (err) { | |
| 1068 | error.Streaming => { | |
| 1069 | // Since `file.stat` is populated, this was previously a file stream; since it is | |
| 1070 | // now not a file stream, it must have changed. | |
| 1071 | return error.FileChanged; | |
| 1072 | }, | |
| 1073 | else => |e| return e, | |
| 1074 | }; | |
| 1063 | 1075 | |
| 1064 | if (stat.size > std.math.maxInt(u32)) | |
| 1065 | return error.FileTooBig; | |
| 1076 | if (stat.inode != file.stat.inode or | |
| 1077 | stat.size != file.stat.size or | |
| 1078 | stat.mtime.nanoseconds != file.stat.mtime.nanoseconds) | |
| 1079 | { | |
| 1080 | return error.FileChanged; | |
| 1081 | } | |
| 1066 | 1082 | |
| 1067 | const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0); | |
| 1083 | const source = try gpa.allocSentinel(u8, @intCast(file.stat.size), 0); | |
| 1068 | 1084 | errdefer gpa.free(source); |
| 1069 | 1085 | |
| 1070 | 1086 | var file_reader = f.reader(io, &.{}); |
| 1071 | 1087 | file_reader.size = stat.size; |
| 1072 | 1088 | file_reader.interface.readSliceAll(source) catch return file_reader.err.?; |
| 1073 | 1089 | |
| 1074 | // Here we do not modify stat fields because this function is the one | |
| 1075 | // used for error reporting. We need to keep the stat fields stale so that | |
| 1076 | // updateFile can know to regenerate ZIR. | |
| 1077 | ||
| 1078 | 1090 | file.source = source; |
| 1079 | 1091 | errdefer comptime unreachable; // don't error after populating `source` |
| 1080 | 1092 | |
| 1081 | return .{ | |
| 1082 | .bytes = source, | |
| 1083 | .stat = .{ | |
| 1084 | .size = stat.size, | |
| 1085 | .inode = stat.inode, | |
| 1086 | .mtime = stat.mtime, | |
| 1087 | }, | |
| 1088 | }; | |
| 1093 | return source; | |
| 1089 | 1094 | } |
| 1090 | 1095 | |
| 1096 | /// This must only be called in error conditions where `stat` *is* populated. It returns the | |
| 1097 | /// parsed AST of the source file, assuming the stat has not changed since it was originally | |
| 1098 | /// loaded. | |
| 1091 | 1099 | pub fn getTree(file: *File, zcu: *const Zcu) GetSourceError!*const Ast { |
| 1092 | 1100 | if (file.tree) |*tree| return tree; |
| 1093 | 1101 | |
| 1094 | 1102 | const source = try file.getSource(zcu); |
| 1095 | file.tree = try .parse(zcu.gpa, source.bytes, file.getMode()); | |
| 1103 | file.tree = try .parse(zcu.gpa, source, file.getMode()); | |
| 1096 | 1104 | return &file.tree.?; |
| 1097 | 1105 | } |
| 1098 | 1106 |