main.zig (41392B) - Raw
1 const std = @import("std"); 2 const builtin = @import("builtin"); 3 const removeComments = @import("comments.zig").removeComments; 4 const parseAndRemoveLineCommands = @import("source_mapping.zig").parseAndRemoveLineCommands; 5 const compile = @import("compile.zig").compile; 6 const Diagnostics = @import("errors.zig").Diagnostics; 7 const cli = @import("cli.zig"); 8 const preprocess = @import("preprocess.zig"); 9 const renderErrorMessage = @import("utils.zig").renderErrorMessage; 10 const openFileNotDir = @import("utils.zig").openFileNotDir; 11 const cvtres = @import("cvtres.zig"); 12 const hasDisjointCodePage = @import("disjoint_code_page.zig").hasDisjointCodePage; 13 const fmtResourceType = @import("res.zig").NameOrOrdinal.fmtResourceType; 14 const aro = @import("aro"); 15 16 var stdout_buffer: [1024]u8 = undefined; 17 18 pub fn main() !void { 19 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; 20 defer std.debug.assert(gpa.deinit() == .ok); 21 const allocator = gpa.allocator(); 22 23 var arena_state = std.heap.ArenaAllocator.init(allocator); 24 defer arena_state.deinit(); 25 const arena = arena_state.allocator(); 26 27 const stderr = std.fs.File.stderr(); 28 const stderr_config = std.io.tty.detectConfig(stderr); 29 30 const args = try std.process.argsAlloc(allocator); 31 defer std.process.argsFree(allocator, args); 32 33 if (args.len < 2) { 34 try renderErrorMessage(std.debug.lockStderrWriter(&.{}), stderr_config, .err, "expected zig lib dir as first argument", .{}); 35 std.process.exit(1); 36 } 37 const zig_lib_dir = args[1]; 38 var cli_args = args[2..]; 39 40 var zig_integration = false; 41 if (cli_args.len > 0 and std.mem.eql(u8, cli_args[0], "--zig-integration")) { 42 zig_integration = true; 43 cli_args = args[3..]; 44 } 45 46 var stdout_writer2 = std.fs.File.stdout().writer(&stdout_buffer); 47 var error_handler: ErrorHandler = switch (zig_integration) { 48 true => .{ 49 .server = .{ 50 .out = &stdout_writer2.interface, 51 .in = undefined, // won't be receiving messages 52 }, 53 }, 54 false => .{ 55 .tty = stderr_config, 56 }, 57 }; 58 59 var options = options: { 60 var cli_diagnostics = cli.Diagnostics.init(allocator); 61 defer cli_diagnostics.deinit(); 62 var options = cli.parse(allocator, cli_args, &cli_diagnostics) catch |err| switch (err) { 63 error.ParseError => { 64 try error_handler.emitCliDiagnostics(allocator, cli_args, &cli_diagnostics); 65 std.process.exit(1); 66 }, 67 else => |e| return e, 68 }; 69 try options.maybeAppendRC(std.fs.cwd()); 70 71 if (!zig_integration) { 72 // print any warnings/notes 73 cli_diagnostics.renderToStdErr(cli_args, stderr_config); 74 // If there was something printed, then add an extra newline separator 75 // so that there is a clear separation between the cli diagnostics and whatever 76 // gets printed after 77 if (cli_diagnostics.errors.items.len > 0) { 78 try stderr.writeAll("\n"); 79 } 80 } 81 break :options options; 82 }; 83 defer options.deinit(); 84 85 if (options.print_help_and_exit) { 86 const stdout = std.fs.File.stdout(); 87 try cli.writeUsage(stdout.deprecatedWriter(), "zig rc"); 88 return; 89 } 90 91 // Don't allow verbose when integrating with Zig via stdout 92 options.verbose = false; 93 94 const stdout_writer = std.fs.File.stdout().deprecatedWriter(); 95 if (options.verbose) { 96 try options.dumpVerbose(stdout_writer); 97 try stdout_writer.writeByte('\n'); 98 } 99 100 var dependencies_list = std.array_list.Managed([]const u8).init(allocator); 101 defer { 102 for (dependencies_list.items) |item| { 103 allocator.free(item); 104 } 105 dependencies_list.deinit(); 106 } 107 const maybe_dependencies_list: ?*std.array_list.Managed([]const u8) = if (options.depfile_path != null) &dependencies_list else null; 108 109 var include_paths = LazyIncludePaths{ 110 .arena = arena, 111 .auto_includes_option = options.auto_includes, 112 .zig_lib_dir = zig_lib_dir, 113 .target_machine_type = options.coff_options.target, 114 }; 115 116 const full_input = full_input: { 117 if (options.input_format == .rc and options.preprocess != .no) { 118 var preprocessed_buf = std.array_list.Managed(u8).init(allocator); 119 errdefer preprocessed_buf.deinit(); 120 121 // We're going to throw away everything except the final preprocessed output anyway, 122 // so we can use a scoped arena for everything else. 123 var aro_arena_state = std.heap.ArenaAllocator.init(allocator); 124 defer aro_arena_state.deinit(); 125 const aro_arena = aro_arena_state.allocator(); 126 127 var comp = aro.Compilation.init(aro_arena, std.fs.cwd()); 128 defer comp.deinit(); 129 130 var argv = std.array_list.Managed([]const u8).init(comp.gpa); 131 defer argv.deinit(); 132 133 try argv.append("arocc"); // dummy command name 134 const resolved_include_paths = try include_paths.get(&error_handler); 135 try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths); 136 try argv.append(switch (options.input_source) { 137 .stdio => "-", 138 .filename => |filename| filename, 139 }); 140 141 if (options.verbose) { 142 try stdout_writer.writeAll("Preprocessor: arocc (built-in)\n"); 143 for (argv.items[0 .. argv.items.len - 1]) |arg| { 144 try stdout_writer.print("{s} ", .{arg}); 145 } 146 try stdout_writer.print("{s}\n\n", .{argv.items[argv.items.len - 1]}); 147 } 148 149 preprocess.preprocess(&comp, preprocessed_buf.writer(), argv.items, maybe_dependencies_list) catch |err| switch (err) { 150 error.GeneratedSourceError => { 151 try error_handler.emitAroDiagnostics(allocator, "failed during preprocessor setup (this is always a bug):", &comp); 152 std.process.exit(1); 153 }, 154 // ArgError can occur if e.g. the .rc file is not found 155 error.ArgError, error.PreprocessError => { 156 try error_handler.emitAroDiagnostics(allocator, "failed during preprocessing:", &comp); 157 std.process.exit(1); 158 }, 159 error.StreamTooLong => { 160 try error_handler.emitMessage(allocator, .err, "failed during preprocessing: maximum file size exceeded", .{}); 161 std.process.exit(1); 162 }, 163 error.OutOfMemory => |e| return e, 164 }; 165 166 break :full_input try preprocessed_buf.toOwnedSlice(); 167 } else { 168 switch (options.input_source) { 169 .stdio => |file| { 170 break :full_input file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { 171 try error_handler.emitMessage(allocator, .err, "unable to read input from stdin: {s}", .{@errorName(err)}); 172 std.process.exit(1); 173 }; 174 }, 175 .filename => |input_filename| { 176 break :full_input std.fs.cwd().readFileAlloc(allocator, input_filename, std.math.maxInt(usize)) catch |err| { 177 try error_handler.emitMessage(allocator, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) }); 178 std.process.exit(1); 179 }; 180 }, 181 } 182 } 183 }; 184 defer allocator.free(full_input); 185 186 if (options.preprocess == .only) { 187 switch (options.output_source) { 188 .stdio => |output_file| { 189 try output_file.writeAll(full_input); 190 }, 191 .filename => |output_filename| { 192 try std.fs.cwd().writeFile(.{ .sub_path = output_filename, .data = full_input }); 193 }, 194 } 195 return; 196 } 197 198 var resources = resources: { 199 const need_intermediate_res = options.output_format == .coff and options.input_format != .res; 200 var res_stream = if (need_intermediate_res) 201 IoStream{ 202 .name = "<in-memory intermediate res>", 203 .intermediate = true, 204 .source = .{ .memory = .empty }, 205 } 206 else if (options.input_format == .res) 207 IoStream.fromIoSource(options.input_source, .input) catch |err| { 208 try error_handler.emitMessage(allocator, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) }); 209 std.process.exit(1); 210 } 211 else 212 IoStream.fromIoSource(options.output_source, .output) catch |err| { 213 try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); 214 std.process.exit(1); 215 }; 216 defer res_stream.deinit(allocator); 217 218 const res_data = res_data: { 219 if (options.input_format != .res) { 220 // Note: We still want to run this when no-preprocess is set because: 221 // 1. We want to print accurate line numbers after removing multiline comments 222 // 2. We want to be able to handle an already-preprocessed input with #line commands in it 223 var mapping_results = parseAndRemoveLineCommands(allocator, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) { 224 error.InvalidLineCommand => { 225 // TODO: Maybe output the invalid line command 226 try error_handler.emitMessage(allocator, .err, "invalid line command in the preprocessed source", .{}); 227 if (options.preprocess == .no) { 228 try error_handler.emitMessage(allocator, .note, "line commands must be of the format: #line <num> \"<path>\"", .{}); 229 } else { 230 try error_handler.emitMessage(allocator, .note, "this is likely to be a bug, please report it", .{}); 231 } 232 std.process.exit(1); 233 }, 234 error.LineNumberOverflow => { 235 // TODO: Better error message 236 try error_handler.emitMessage(allocator, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)}); 237 std.process.exit(1); 238 }, 239 error.OutOfMemory => |e| return e, 240 }; 241 defer mapping_results.mappings.deinit(allocator); 242 243 const default_code_page = options.default_code_page orelse .windows1252; 244 const has_disjoint_code_page = hasDisjointCodePage(mapping_results.result, &mapping_results.mappings, default_code_page); 245 246 const final_input = try removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings); 247 248 var diagnostics = Diagnostics.init(allocator); 249 defer diagnostics.deinit(); 250 251 var output_buffer: [4096]u8 = undefined; 252 var res_stream_writer = res_stream.source.writer(allocator).adaptToNewApi(&output_buffer); 253 const output_buffered_stream = &res_stream_writer.new_interface; 254 255 compile(allocator, final_input, output_buffered_stream, .{ 256 .cwd = std.fs.cwd(), 257 .diagnostics = &diagnostics, 258 .source_mappings = &mapping_results.mappings, 259 .dependencies_list = maybe_dependencies_list, 260 .ignore_include_env_var = options.ignore_include_env_var, 261 .extra_include_paths = options.extra_include_paths.items, 262 .system_include_paths = try include_paths.get(&error_handler), 263 .default_language_id = options.default_language_id, 264 .default_code_page = default_code_page, 265 .disjoint_code_page = has_disjoint_code_page, 266 .verbose = options.verbose, 267 .null_terminate_string_table_strings = options.null_terminate_string_table_strings, 268 .max_string_literal_codepoints = options.max_string_literal_codepoints, 269 .silent_duplicate_control_ids = options.silent_duplicate_control_ids, 270 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page, 271 }) catch |err| switch (err) { 272 error.ParseError, error.CompileError => { 273 try error_handler.emitDiagnostics(allocator, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings); 274 // Delete the output file on error 275 res_stream.cleanupAfterError(); 276 std.process.exit(1); 277 }, 278 else => |e| return e, 279 }; 280 281 try output_buffered_stream.flush(); 282 283 // print any warnings/notes 284 if (!zig_integration) { 285 diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings); 286 } 287 288 // write the depfile 289 if (options.depfile_path) |depfile_path| { 290 var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| { 291 try error_handler.emitMessage(allocator, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); 292 std.process.exit(1); 293 }; 294 defer depfile.close(); 295 296 var depfile_buffer: [1024]u8 = undefined; 297 var depfile_writer = depfile.writer(&depfile_buffer); 298 switch (options.depfile_fmt) { 299 .json => { 300 var write_stream: std.json.Stringify = .{ 301 .writer = &depfile_writer.interface, 302 .options = .{ .whitespace = .indent_2 }, 303 }; 304 305 try write_stream.beginArray(); 306 for (dependencies_list.items) |dep_path| { 307 try write_stream.write(dep_path); 308 } 309 try write_stream.endArray(); 310 }, 311 } 312 try depfile_writer.interface.flush(); 313 } 314 } 315 316 if (options.output_format != .coff) return; 317 318 break :res_data res_stream.source.readAll(allocator) catch |err| { 319 try error_handler.emitMessage(allocator, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) }); 320 std.process.exit(1); 321 }; 322 }; 323 // No need to keep the res_data around after parsing the resources from it 324 defer res_data.deinit(allocator); 325 326 std.debug.assert(options.output_format == .coff); 327 328 // TODO: Maybe use a buffered file reader instead of reading file into memory -> fbs 329 var res_reader: std.Io.Reader = .fixed(res_data.bytes); 330 break :resources cvtres.parseRes(allocator, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| { 331 // TODO: Better errors 332 try error_handler.emitMessage(allocator, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) }); 333 std.process.exit(1); 334 }; 335 }; 336 defer resources.deinit(); 337 338 var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| { 339 try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) }); 340 std.process.exit(1); 341 }; 342 defer coff_stream.deinit(allocator); 343 344 var coff_output_buffer: [4096]u8 = undefined; 345 var coff_output_buffered_stream = coff_stream.source.writer(allocator).adaptToNewApi(&coff_output_buffer); 346 347 var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} }; 348 cvtres.writeCoff(allocator, &coff_output_buffered_stream.new_interface, resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| { 349 switch (err) { 350 error.DuplicateResource => { 351 const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource]; 352 try error_handler.emitMessage(allocator, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{ 353 duplicate_resource.name_value, 354 fmtResourceType(duplicate_resource.type_value), 355 duplicate_resource.language, 356 }); 357 }, 358 error.ResourceDataTooLong => { 359 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource]; 360 try error_handler.emitMessage(allocator, .err, "resource has a data length that is too large to be written into a coff section", .{}); 361 try error_handler.emitMessage(allocator, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{ 362 overflow_resource.name_value, 363 fmtResourceType(overflow_resource.type_value), 364 overflow_resource.language, 365 }); 366 }, 367 error.TotalResourceDataTooLong => { 368 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource]; 369 try error_handler.emitMessage(allocator, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{}); 370 try error_handler.emitMessage(allocator, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{ 371 overflow_resource.name_value, 372 fmtResourceType(overflow_resource.type_value), 373 overflow_resource.language, 374 }); 375 }, 376 else => { 377 try error_handler.emitMessage(allocator, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) }); 378 }, 379 } 380 // Delete the output file on error 381 coff_stream.cleanupAfterError(); 382 std.process.exit(1); 383 }; 384 385 try coff_output_buffered_stream.new_interface.flush(); 386 } 387 388 const IoStream = struct { 389 name: []const u8, 390 intermediate: bool, 391 source: Source, 392 393 pub const IoDirection = enum { input, output }; 394 395 pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !IoStream { 396 return .{ 397 .name = switch (source) { 398 .filename => |filename| filename, 399 .stdio => switch (io) { 400 .input => "<stdin>", 401 .output => "<stdout>", 402 }, 403 }, 404 .intermediate = false, 405 .source = try Source.fromIoSource(source, io), 406 }; 407 } 408 409 pub fn deinit(self: *IoStream, allocator: std.mem.Allocator) void { 410 self.source.deinit(allocator); 411 } 412 413 pub fn cleanupAfterError(self: *IoStream) void { 414 switch (self.source) { 415 .file => |file| { 416 // Delete the output file on error 417 file.close(); 418 // Failing to delete is not really a big deal, so swallow any errors 419 std.fs.cwd().deleteFile(self.name) catch {}; 420 }, 421 .stdio, .memory, .closed => return, 422 } 423 } 424 425 pub const Source = union(enum) { 426 file: std.fs.File, 427 stdio: std.fs.File, 428 memory: std.ArrayListUnmanaged(u8), 429 /// The source has been closed and any usage of the Source in this state is illegal (except deinit). 430 closed: void, 431 432 pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !Source { 433 switch (source) { 434 .filename => |filename| return .{ 435 .file = switch (io) { 436 .input => try openFileNotDir(std.fs.cwd(), filename, .{}), 437 .output => try std.fs.cwd().createFile(filename, .{}), 438 }, 439 }, 440 .stdio => |file| return .{ .stdio = file }, 441 } 442 } 443 444 pub fn deinit(self: *Source, allocator: std.mem.Allocator) void { 445 switch (self.*) { 446 .file => |file| file.close(), 447 .stdio => {}, 448 .memory => |*list| list.deinit(allocator), 449 .closed => {}, 450 } 451 } 452 453 pub const Data = struct { 454 bytes: []const u8, 455 needs_free: bool, 456 457 pub fn deinit(self: Data, allocator: std.mem.Allocator) void { 458 if (self.needs_free) { 459 allocator.free(self.bytes); 460 } 461 } 462 }; 463 464 pub fn readAll(self: Source, allocator: std.mem.Allocator) !Data { 465 return switch (self) { 466 inline .file, .stdio => |file| .{ 467 .bytes = try file.readToEndAlloc(allocator, std.math.maxInt(usize)), 468 .needs_free = true, 469 }, 470 .memory => |list| .{ .bytes = list.items, .needs_free = false }, 471 .closed => unreachable, 472 }; 473 } 474 475 pub const WriterContext = struct { 476 self: *Source, 477 allocator: std.mem.Allocator, 478 }; 479 pub const WriteError = std.mem.Allocator.Error || std.fs.File.WriteError; 480 pub const Writer = std.io.GenericWriter(WriterContext, WriteError, write); 481 482 pub fn write(ctx: WriterContext, bytes: []const u8) WriteError!usize { 483 switch (ctx.self.*) { 484 inline .file, .stdio => |file| return file.write(bytes), 485 .memory => |*list| { 486 try list.appendSlice(ctx.allocator, bytes); 487 return bytes.len; 488 }, 489 .closed => unreachable, 490 } 491 } 492 493 pub fn writer(self: *Source, allocator: std.mem.Allocator) Writer { 494 return .{ .context = .{ .self = self, .allocator = allocator } }; 495 } 496 }; 497 }; 498 499 const LazyIncludePaths = struct { 500 arena: std.mem.Allocator, 501 auto_includes_option: cli.Options.AutoIncludes, 502 zig_lib_dir: []const u8, 503 target_machine_type: std.coff.MachineType, 504 resolved_include_paths: ?[]const []const u8 = null, 505 506 pub fn get(self: *LazyIncludePaths, error_handler: *ErrorHandler) ![]const []const u8 { 507 if (self.resolved_include_paths) |include_paths| 508 return include_paths; 509 510 return getIncludePaths(self.arena, self.auto_includes_option, self.zig_lib_dir, self.target_machine_type) catch |err| switch (err) { 511 error.OutOfMemory => |e| return e, 512 else => |e| { 513 switch (e) { 514 error.UnsupportedAutoIncludesMachineType => { 515 try error_handler.emitMessage(self.arena, .err, "automatic include path detection is not supported for target '{s}'", .{@tagName(self.target_machine_type)}); 516 }, 517 error.MsvcIncludesNotFound => { 518 try error_handler.emitMessage(self.arena, .err, "MSVC include paths could not be automatically detected", .{}); 519 }, 520 error.MingwIncludesNotFound => { 521 try error_handler.emitMessage(self.arena, .err, "MinGW include paths could not be automatically detected", .{}); 522 }, 523 } 524 try error_handler.emitMessage(self.arena, .note, "to disable auto includes, use the option /:auto-includes none", .{}); 525 std.process.exit(1); 526 }, 527 }; 528 } 529 }; 530 531 fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8, target_machine_type: std.coff.MachineType) ![]const []const u8 { 532 if (auto_includes_option == .none) return &[_][]const u8{}; 533 534 const includes_arch: std.Target.Cpu.Arch = switch (target_machine_type) { 535 .X64 => .x86_64, 536 .I386 => .x86, 537 .ARMNT => .thumb, 538 .ARM64 => .aarch64, 539 .ARM64EC => .aarch64, 540 .ARM64X => .aarch64, 541 .IA64, .EBC => { 542 return error.UnsupportedAutoIncludesMachineType; 543 }, 544 // The above cases are exhaustive of all the `MachineType`s supported (see supported_targets in cvtres.zig) 545 // This is enforced by the argument parser in cli.zig. 546 else => unreachable, 547 }; 548 549 var includes = auto_includes_option; 550 if (builtin.target.os.tag != .windows) { 551 switch (includes) { 552 .none => unreachable, 553 // MSVC can't be found when the host isn't Windows, so short-circuit. 554 .msvc => return error.MsvcIncludesNotFound, 555 // Skip straight to gnu since we won't be able to detect MSVC on non-Windows hosts. 556 .any => includes = .gnu, 557 .gnu => {}, 558 } 559 } 560 561 while (true) { 562 switch (includes) { 563 .none => unreachable, 564 .any, .msvc => { 565 // MSVC is only detectable on Windows targets. This unreachable is to signify 566 // that .any and .msvc should be dealt with on non-Windows targets before this point, 567 // since getting MSVC include paths uses Windows-only APIs. 568 if (builtin.target.os.tag != .windows) unreachable; 569 570 const target_query: std.Target.Query = .{ 571 .os_tag = .windows, 572 .cpu_arch = includes_arch, 573 .abi = .msvc, 574 }; 575 const target = std.zig.resolveTargetQueryOrFatal(target_query); 576 const is_native_abi = target_query.isNativeAbi(); 577 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch { 578 if (includes == .any) { 579 // fall back to mingw 580 includes = .gnu; 581 continue; 582 } 583 return error.MsvcIncludesNotFound; 584 }; 585 if (detected_libc.libc_include_dir_list.len == 0) { 586 if (includes == .any) { 587 // fall back to mingw 588 includes = .gnu; 589 continue; 590 } 591 return error.MsvcIncludesNotFound; 592 } 593 return detected_libc.libc_include_dir_list; 594 }, 595 .gnu => { 596 const target_query: std.Target.Query = .{ 597 .os_tag = .windows, 598 .cpu_arch = includes_arch, 599 .abi = .gnu, 600 }; 601 const target = std.zig.resolveTargetQueryOrFatal(target_query); 602 const is_native_abi = target_query.isNativeAbi(); 603 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) { 604 error.OutOfMemory => |e| return e, 605 else => return error.MingwIncludesNotFound, 606 }; 607 return detected_libc.libc_include_dir_list; 608 }, 609 } 610 } 611 } 612 613 const ErrorBundle = std.zig.ErrorBundle; 614 const SourceMappings = @import("source_mapping.zig").SourceMappings; 615 616 const ErrorHandler = union(enum) { 617 server: std.zig.Server, 618 tty: std.io.tty.Config, 619 620 pub fn emitCliDiagnostics( 621 self: *ErrorHandler, 622 allocator: std.mem.Allocator, 623 args: []const []const u8, 624 diagnostics: *cli.Diagnostics, 625 ) !void { 626 switch (self.*) { 627 .server => |*server| { 628 var error_bundle = try cliDiagnosticsToErrorBundle(allocator, diagnostics); 629 defer error_bundle.deinit(allocator); 630 631 try server.serveErrorBundle(error_bundle); 632 }, 633 .tty => { 634 diagnostics.renderToStdErr(args, self.tty); 635 }, 636 } 637 } 638 639 pub fn emitAroDiagnostics( 640 self: *ErrorHandler, 641 allocator: std.mem.Allocator, 642 fail_msg: []const u8, 643 comp: *aro.Compilation, 644 ) !void { 645 switch (self.*) { 646 .server => |*server| { 647 var error_bundle = try aroDiagnosticsToErrorBundle(allocator, fail_msg, comp); 648 defer error_bundle.deinit(allocator); 649 650 try server.serveErrorBundle(error_bundle); 651 }, 652 .tty => { 653 // extra newline to separate this line from the aro errors 654 const stderr = std.debug.lockStderrWriter(&.{}); 655 defer std.debug.unlockStderrWriter(); 656 try renderErrorMessage(stderr, self.tty, .err, "{s}\n", .{fail_msg}); 657 aro.Diagnostics.render(comp, self.tty); 658 }, 659 } 660 } 661 662 pub fn emitDiagnostics( 663 self: *ErrorHandler, 664 allocator: std.mem.Allocator, 665 cwd: std.fs.Dir, 666 source: []const u8, 667 diagnostics: *Diagnostics, 668 mappings: SourceMappings, 669 ) !void { 670 switch (self.*) { 671 .server => |*server| { 672 var error_bundle = try diagnosticsToErrorBundle(allocator, source, diagnostics, mappings); 673 defer error_bundle.deinit(allocator); 674 675 try server.serveErrorBundle(error_bundle); 676 }, 677 .tty => { 678 diagnostics.renderToStdErr(cwd, source, self.tty, mappings); 679 }, 680 } 681 } 682 683 pub fn emitMessage( 684 self: *ErrorHandler, 685 allocator: std.mem.Allocator, 686 msg_type: @import("utils.zig").ErrorMessageType, 687 comptime format: []const u8, 688 args: anytype, 689 ) !void { 690 switch (self.*) { 691 .server => |*server| { 692 // only emit errors 693 if (msg_type != .err) return; 694 695 var error_bundle = try errorStringToErrorBundle(allocator, format, args); 696 defer error_bundle.deinit(allocator); 697 698 try server.serveErrorBundle(error_bundle); 699 }, 700 .tty => { 701 const stderr = std.debug.lockStderrWriter(&.{}); 702 defer std.debug.unlockStderrWriter(); 703 try renderErrorMessage(stderr, self.tty, msg_type, format, args); 704 }, 705 } 706 } 707 }; 708 709 fn cliDiagnosticsToErrorBundle( 710 gpa: std.mem.Allocator, 711 diagnostics: *cli.Diagnostics, 712 ) !ErrorBundle { 713 @branchHint(.cold); 714 715 var bundle: ErrorBundle.Wip = undefined; 716 try bundle.init(gpa); 717 errdefer bundle.deinit(); 718 719 try bundle.addRootErrorMessage(.{ 720 .msg = try bundle.addString("invalid command line option(s)"), 721 }); 722 723 var cur_err: ?ErrorBundle.ErrorMessage = null; 724 var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty; 725 defer cur_notes.deinit(gpa); 726 for (diagnostics.errors.items) |err_details| { 727 switch (err_details.type) { 728 .err => { 729 if (cur_err) |err| { 730 try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); 731 } 732 cur_err = .{ 733 .msg = try bundle.addString(err_details.msg.items), 734 }; 735 cur_notes.clearRetainingCapacity(); 736 }, 737 .warning => cur_err = null, 738 .note => { 739 if (cur_err == null) continue; 740 cur_err.?.notes_len += 1; 741 try cur_notes.append(gpa, .{ 742 .msg = try bundle.addString(err_details.msg.items), 743 }); 744 }, 745 } 746 } 747 if (cur_err) |err| { 748 try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); 749 } 750 751 return try bundle.toOwnedBundle(""); 752 } 753 754 fn diagnosticsToErrorBundle( 755 gpa: std.mem.Allocator, 756 source: []const u8, 757 diagnostics: *Diagnostics, 758 mappings: SourceMappings, 759 ) !ErrorBundle { 760 @branchHint(.cold); 761 762 var bundle: ErrorBundle.Wip = undefined; 763 try bundle.init(gpa); 764 errdefer bundle.deinit(); 765 766 var msg_buf: std.ArrayListUnmanaged(u8) = .empty; 767 defer msg_buf.deinit(gpa); 768 var cur_err: ?ErrorBundle.ErrorMessage = null; 769 var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty; 770 defer cur_notes.deinit(gpa); 771 for (diagnostics.errors.items) |err_details| { 772 switch (err_details.type) { 773 .hint => continue, 774 // Clear the current error so that notes don't bleed into unassociated errors 775 .warning => { 776 cur_err = null; 777 continue; 778 }, 779 .note => if (cur_err == null) continue, 780 .err => {}, 781 } 782 const corresponding_span = mappings.getCorrespondingSpan(err_details.token.line_number).?; 783 const err_line = corresponding_span.start_line; 784 const err_filename = mappings.files.get(corresponding_span.filename_offset); 785 786 const source_line_start = err_details.token.getLineStartForErrorDisplay(source); 787 // Treat tab stops as 1 column wide for error display purposes, 788 // and add one to get a 1-based column 789 const column = err_details.token.calculateColumn(source, 1, source_line_start) + 1; 790 791 msg_buf.clearRetainingCapacity(); 792 try err_details.render(msg_buf.writer(gpa), source, diagnostics.strings.items); 793 794 const src_loc = src_loc: { 795 var src_loc: ErrorBundle.SourceLocation = .{ 796 .src_path = try bundle.addString(err_filename), 797 .line = @intCast(err_line - 1), // 1-based -> 0-based 798 .column = @intCast(column - 1), // 1-based -> 0-based 799 .span_start = 0, 800 .span_main = 0, 801 .span_end = 0, 802 }; 803 if (err_details.print_source_line) { 804 const source_line = err_details.token.getLineForErrorDisplay(source, source_line_start); 805 const visual_info = err_details.visualTokenInfo(source_line_start, source_line_start + source_line.len, source); 806 src_loc.span_start = @intCast(visual_info.point_offset - visual_info.before_len); 807 src_loc.span_main = @intCast(visual_info.point_offset); 808 src_loc.span_end = @intCast(visual_info.point_offset + 1 + visual_info.after_len); 809 src_loc.source_line = try bundle.addString(source_line); 810 } 811 break :src_loc try bundle.addSourceLocation(src_loc); 812 }; 813 814 switch (err_details.type) { 815 .err => { 816 if (cur_err) |err| { 817 try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); 818 } 819 cur_err = .{ 820 .msg = try bundle.addString(msg_buf.items), 821 .src_loc = src_loc, 822 }; 823 cur_notes.clearRetainingCapacity(); 824 }, 825 .note => { 826 cur_err.?.notes_len += 1; 827 try cur_notes.append(gpa, .{ 828 .msg = try bundle.addString(msg_buf.items), 829 .src_loc = src_loc, 830 }); 831 }, 832 .warning, .hint => unreachable, 833 } 834 } 835 if (cur_err) |err| { 836 try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); 837 } 838 839 return try bundle.toOwnedBundle(""); 840 } 841 842 fn flushErrorMessageIntoBundle(wip: *ErrorBundle.Wip, msg: ErrorBundle.ErrorMessage, notes: []const ErrorBundle.ErrorMessage) !void { 843 try wip.addRootErrorMessage(msg); 844 const notes_start = try wip.reserveNotes(@intCast(notes.len)); 845 for (notes_start.., notes) |i, note| { 846 wip.extra.items[i] = @intFromEnum(wip.addErrorMessageAssumeCapacity(note)); 847 } 848 } 849 850 fn errorStringToErrorBundle(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) !ErrorBundle { 851 @branchHint(.cold); 852 var bundle: ErrorBundle.Wip = undefined; 853 try bundle.init(allocator); 854 errdefer bundle.deinit(); 855 try bundle.addRootErrorMessage(.{ 856 .msg = try bundle.printString(format, args), 857 }); 858 return try bundle.toOwnedBundle(""); 859 } 860 861 fn aroDiagnosticsToErrorBundle( 862 gpa: std.mem.Allocator, 863 fail_msg: []const u8, 864 comp: *aro.Compilation, 865 ) !ErrorBundle { 866 @branchHint(.cold); 867 868 var bundle: ErrorBundle.Wip = undefined; 869 try bundle.init(gpa); 870 errdefer bundle.deinit(); 871 872 try bundle.addRootErrorMessage(.{ 873 .msg = try bundle.addString(fail_msg), 874 }); 875 876 var msg_writer = MsgWriter.init(gpa); 877 defer msg_writer.deinit(); 878 var cur_err: ?ErrorBundle.ErrorMessage = null; 879 var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty; 880 defer cur_notes.deinit(gpa); 881 for (comp.diagnostics.list.items) |msg| { 882 switch (msg.kind) { 883 // Clear the current error so that notes don't bleed into unassociated errors 884 .off, .warning => { 885 cur_err = null; 886 continue; 887 }, 888 .note => if (cur_err == null) continue, 889 .@"fatal error", .@"error" => {}, 890 .default => unreachable, 891 } 892 msg_writer.resetRetainingCapacity(); 893 aro.Diagnostics.renderMessage(comp, &msg_writer, msg); 894 895 const src_loc = src_loc: { 896 if (msg_writer.path) |src_path| { 897 var src_loc: ErrorBundle.SourceLocation = .{ 898 .src_path = try bundle.addString(src_path), 899 .line = msg_writer.line - 1, // 1-based -> 0-based 900 .column = msg_writer.col - 1, // 1-based -> 0-based 901 .span_start = 0, 902 .span_main = 0, 903 .span_end = 0, 904 }; 905 if (msg_writer.source_line) |source_line| { 906 src_loc.span_start = msg_writer.span_main; 907 src_loc.span_main = msg_writer.span_main; 908 src_loc.span_end = msg_writer.span_main; 909 src_loc.source_line = try bundle.addString(source_line); 910 } 911 break :src_loc try bundle.addSourceLocation(src_loc); 912 } 913 break :src_loc ErrorBundle.SourceLocationIndex.none; 914 }; 915 916 switch (msg.kind) { 917 .@"fatal error", .@"error" => { 918 if (cur_err) |err| { 919 try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); 920 } 921 cur_err = .{ 922 .msg = try bundle.addString(msg_writer.buf.items), 923 .src_loc = src_loc, 924 }; 925 cur_notes.clearRetainingCapacity(); 926 }, 927 .note => { 928 cur_err.?.notes_len += 1; 929 try cur_notes.append(gpa, .{ 930 .msg = try bundle.addString(msg_writer.buf.items), 931 .src_loc = src_loc, 932 }); 933 }, 934 .off, .warning, .default => unreachable, 935 } 936 } 937 if (cur_err) |err| { 938 try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); 939 } 940 941 return try bundle.toOwnedBundle(""); 942 } 943 944 // Similar to aro.Diagnostics.MsgWriter but: 945 // - Writers to an ArrayList 946 // - Only prints the message itself (no location, source line, error: prefix, etc) 947 // - Keeps track of source path/line/col instead 948 const MsgWriter = struct { 949 buf: std.array_list.Managed(u8), 950 path: ?[]const u8 = null, 951 // 1-indexed 952 line: u32 = undefined, 953 col: u32 = undefined, 954 source_line: ?[]const u8 = null, 955 span_main: u32 = undefined, 956 957 fn init(allocator: std.mem.Allocator) MsgWriter { 958 return .{ 959 .buf = std.array_list.Managed(u8).init(allocator), 960 }; 961 } 962 963 fn deinit(m: *MsgWriter) void { 964 m.buf.deinit(); 965 } 966 967 fn resetRetainingCapacity(m: *MsgWriter) void { 968 m.buf.clearRetainingCapacity(); 969 m.path = null; 970 m.source_line = null; 971 } 972 973 pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void { 974 m.buf.writer().print(fmt, args) catch {}; 975 } 976 977 pub fn write(m: *MsgWriter, msg: []const u8) void { 978 m.buf.writer().writeAll(msg) catch {}; 979 } 980 981 pub fn setColor(m: *MsgWriter, color: std.io.tty.Color) void { 982 _ = m; 983 _ = color; 984 } 985 986 pub fn location(m: *MsgWriter, path: []const u8, line: u32, col: u32) void { 987 m.path = path; 988 m.line = line; 989 m.col = col; 990 } 991 992 pub fn start(m: *MsgWriter, kind: aro.Diagnostics.Kind) void { 993 _ = m; 994 _ = kind; 995 } 996 997 pub fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void { 998 _ = end_with_splice; 999 m.source_line = maybe_line; 1000 m.span_main = col; 1001 } 1002 };