Commit bc97a5662d

Veikka Tuominen <git@vexu.eu>
2022-12-12 12:16:09
Sema: display cimport errors from clang
1 parent 886fa45
src/clang.zig
@@ -1897,13 +1897,13 @@ pub const OffsetOfNode_Kind = enum(c_int) {
     Base,
 };
 
-pub const Stage2ErrorMsg = extern struct {
+pub const ErrorMsg = extern struct {
     filename_ptr: ?[*]const u8,
     filename_len: usize,
     msg_ptr: [*]const u8,
     msg_len: usize,
     // valid until the ASTUnit is freed
-    source: ?[*]const u8,
+    source: ?[*:0]const u8,
     // 0 based
     line: c_uint,
     // 0 based
@@ -1912,14 +1912,14 @@ pub const Stage2ErrorMsg = extern struct {
     offset: c_uint,
 
     pub const delete = ZigClangErrorMsg_delete;
-    extern fn ZigClangErrorMsg_delete(ptr: [*]Stage2ErrorMsg, len: usize) void;
+    extern fn ZigClangErrorMsg_delete(ptr: [*]ErrorMsg, len: usize) void;
 };
 
 pub const LoadFromCommandLine = ZigClangLoadFromCommandLine;
 extern fn ZigClangLoadFromCommandLine(
     args_begin: [*]?[*]const u8,
     args_end: [*]?[*]const u8,
-    errors_ptr: *[*]Stage2ErrorMsg,
+    errors_ptr: *[*]ErrorMsg,
     errors_len: *usize,
     resources_path: [*:0]const u8,
 ) ?*ASTUnit;
src/Compilation.zig
@@ -31,6 +31,7 @@ const clangMain = @import("main.zig").clangMain;
 const Module = @import("Module.zig");
 const Cache = @import("Cache.zig");
 const translate_c = @import("translate_c.zig");
+const clang = @import("clang.zig");
 const c_codegen = @import("codegen/c.zig");
 const ThreadPool = @import("ThreadPool.zig");
 const WaitGroup = @import("WaitGroup.zig");
@@ -2749,6 +2750,9 @@ pub fn totalErrorCount(self: *Compilation) usize {
             const decl = module.declPtr(key);
             if (decl.getFileScope().okToReportErrors()) {
                 total += 1;
+                if (module.cimport_errors.get(key)) |errors| {
+                    total += errors.len;
+                }
             }
         }
         if (module.emit_h) |emit_h| {
@@ -2858,6 +2862,23 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
                 // We'll try again once parsing succeeds.
                 if (decl.getFileScope().okToReportErrors()) {
                     try AllErrors.add(module, &arena, &errors, entry.value_ptr.*.*);
+                    if (module.cimport_errors.get(entry.key_ptr.*)) |cimport_errors| for (cimport_errors) |c_error| {
+                        if (c_error.path) |some|
+                            try errors.append(.{
+                                .src = .{
+                                    .src_path = try arena_allocator.dupe(u8, std.mem.span(some)),
+                                    .span = .{ .start = c_error.offset, .end = c_error.offset + 1, .main = c_error.offset },
+                                    .msg = try arena_allocator.dupe(u8, std.mem.span(c_error.msg)),
+                                    .line = c_error.line,
+                                    .column = c_error.column,
+                                    .source_line = if (c_error.source_line) |line| try arena_allocator.dupe(u8, std.mem.span(line)) else null,
+                                },
+                            })
+                        else
+                            try errors.append(.{
+                                .plain = .{ .msg = try arena_allocator.dupe(u8, std.mem.span(c_error.msg)) },
+                            });
+                    };
                 }
             }
         }
@@ -3524,7 +3545,7 @@ test "cImport" {
 
 const CImportResult = struct {
     out_zig_path: []u8,
-    errors: []translate_c.ClangErrMsg,
+    errors: []clang.ErrorMsg,
 };
 
 /// Caller owns returned memory.
@@ -3599,7 +3620,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult {
 
         const c_headers_dir_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{"include"});
         const c_headers_dir_path_z = try arena.dupeZ(u8, c_headers_dir_path);
-        var clang_errors: []translate_c.ClangErrMsg = &[0]translate_c.ClangErrMsg{};
+        var clang_errors: []clang.ErrorMsg = &[0]clang.ErrorMsg{};
         var tree = translate_c.translate(
             comp.gpa,
             new_argv.ptr,
@@ -3665,7 +3686,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult {
     }
     return CImportResult{
         .out_zig_path = out_zig_path,
-        .errors = &[0]translate_c.ClangErrMsg{},
+        .errors = &[0]clang.ErrorMsg{},
     };
 }
 
src/main.zig
@@ -19,6 +19,7 @@ const introspect = @import("introspect.zig");
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 const wasi_libc = @import("wasi_libc.zig");
 const translate_c = @import("translate_c.zig");
+const clang = @import("clang.zig");
 const Cache = @import("Cache.zig");
 const target_util = @import("target.zig");
 const ThreadPool = @import("ThreadPool.zig");
@@ -3552,7 +3553,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void
 
         const c_headers_dir_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{"include"});
         const c_headers_dir_path_z = try arena.dupeZ(u8, c_headers_dir_path);
-        var clang_errors: []translate_c.ClangErrMsg = &[0]translate_c.ClangErrMsg{};
+        var clang_errors: []clang.ErrorMsg = &[0]clang.ErrorMsg{};
         var tree = translate_c.translate(
             comp.gpa,
             new_argv.ptr,
src/Module.zig
@@ -31,6 +31,7 @@ const target_util = @import("target.zig");
 const build_options = @import("build_options");
 const Liveness = @import("Liveness.zig");
 const isUpDir = @import("introspect.zig").isUpDir;
+const clang = @import("clang.zig");
 
 /// General-purpose allocator. Used for both temporary and long-term storage.
 gpa: Allocator,
@@ -111,6 +112,9 @@ failed_embed_files: std.AutoArrayHashMapUnmanaged(*EmbedFile, *ErrorMsg) = .{},
 /// Using a map here for consistency with the other fields here.
 /// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator.
 failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},
+/// If a decl failed due to a cimport error, the corresponding Clang errors
+/// are stored here.
+cimport_errors: std.AutoArrayHashMapUnmanaged(Decl.Index, []CImportError) = .{},
 
 /// Candidates for deletion. After a semantic analysis update completes, this list
 /// contains Decls that need to be deleted if they end up having no references to them.
@@ -172,6 +176,21 @@ reference_table: std.AutoHashMapUnmanaged(Decl.Index, struct {
     src: LazySrcLoc,
 }) = .{},
 
+pub const CImportError = struct {
+    offset: u32,
+    line: u32,
+    column: u32,
+    path: ?[*:0]u8,
+    source_line: ?[*:0]u8,
+    msg: [*:0]u8,
+
+    pub fn deinit(err: CImportError, gpa: Allocator) void {
+        if (err.path) |some| gpa.free(std.mem.span(some));
+        if (err.source_line) |some| gpa.free(std.mem.span(some));
+        gpa.free(std.mem.span(err.msg));
+    }
+};
+
 pub const StringLiteralContext = struct {
     bytes: *ArrayListUnmanaged(u8),
 
@@ -3449,6 +3468,11 @@ pub fn deinit(mod: *Module) void {
     }
     mod.failed_exports.deinit(gpa);
 
+    for (mod.cimport_errors.values()) |errs| {
+        for (errs) |err| err.deinit(gpa);
+    }
+    mod.cimport_errors.deinit(gpa);
+
     mod.compile_log_decls.deinit(gpa);
 
     for (mod.decl_exports.values()) |*export_list| {
@@ -5381,6 +5405,9 @@ pub fn clearDecl(
     if (mod.failed_decls.fetchSwapRemove(decl_index)) |kv| {
         kv.value.destroy(gpa);
     }
+    if (mod.cimport_errors.fetchSwapRemove(decl_index)) |kv| {
+        for (kv.value) |err| err.deinit(gpa);
+    }
     if (mod.emit_h) |emit_h| {
         if (emit_h.failed_decls.fetchSwapRemove(decl_index)) |kv| {
             kv.value.destroy(gpa);
@@ -5768,6 +5795,9 @@ fn markOutdatedDecl(mod: *Module, decl_index: Decl.Index) !void {
     if (mod.failed_decls.fetchSwapRemove(decl_index)) |kv| {
         kv.value.destroy(mod.gpa);
     }
+    if (mod.cimport_errors.fetchSwapRemove(decl_index)) |kv| {
+        for (kv.value) |err| err.deinit(mod.gpa);
+    }
     if (decl.has_tv and decl.owns_tv) {
         if (decl.val.castTag(.function)) |payload| {
             const func = payload.data;
src/Sema.zig
@@ -5130,20 +5130,58 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
 
     if (c_import_res.errors.len != 0) {
         const msg = msg: {
+            defer @import("clang.zig").ErrorMsg.delete(c_import_res.errors.ptr, c_import_res.errors.len);
+
             const msg = try sema.errMsg(&child_block, src, "C import failed", .{});
             errdefer msg.destroy(sema.gpa);
 
             if (!mod.comp.bin_file.options.link_libc)
                 try sema.errNote(&child_block, src, msg, "libc headers not available; compilation does not link against libc", .{});
 
-            for (c_import_res.errors) |_| {
-                // TODO integrate with LazySrcLoc
-                // try mod.errNoteNonLazy(.{}, msg, "{s}", .{clang_err.msg_ptr[0..clang_err.msg_len]});
-                // if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)",
-                // clang_err.line + 1,
-                // clang_err.column + 1,
+            const gop = try sema.mod.cimport_errors.getOrPut(sema.gpa, sema.owner_decl_index);
+            if (!gop.found_existing) {
+                var errs = try std.ArrayListUnmanaged(Module.CImportError).initCapacity(sema.gpa, c_import_res.errors.len);
+                errdefer {
+                    for (errs.items) |err| err.deinit(sema.gpa);
+                    errs.deinit(sema.gpa);
+                }
+
+                for (c_import_res.errors) |c_error| {
+                    const path = if (c_error.filename_ptr) |some|
+                        try sema.gpa.dupeZ(u8, some[0..c_error.filename_len])
+                    else
+                        null;
+                    errdefer if (path) |some| sema.gpa.free(some);
+
+                    const c_msg = try sema.gpa.dupeZ(u8, c_error.msg_ptr[0..c_error.msg_len]);
+                    errdefer sema.gpa.free(c_msg);
+
+                    const line = line: {
+                        const source = c_error.source orelse break :line null;
+                        var start = c_error.offset;
+                        while (start > 0) : (start -= 1) {
+                            if (source[start - 1] == '\n') break;
+                        }
+                        var end = c_error.offset;
+                        while (true) : (end += 1) {
+                            if (source[end] == 0) break;
+                            if (source[end] == '\n') break;
+                        }
+                        break :line try sema.gpa.dupeZ(u8, source[start..end]);
+                    };
+                    errdefer if (line) |some| sema.gpa.free(some);
+
+                    errs.appendAssumeCapacity(.{
+                        .path = path orelse null,
+                        .source_line = line orelse null,
+                        .line = c_error.line,
+                        .column = c_error.column,
+                        .offset = c_error.offset,
+                        .msg = c_msg,
+                    });
+                }
+                gop.value_ptr.* = errs.items;
             }
-            @import("clang.zig").Stage2ErrorMsg.delete(c_import_res.errors.ptr, c_import_res.errors.len);
             break :msg msg;
         };
         return sema.failWithOwnedErrorMsg(msg);
src/translate_c.zig
@@ -13,8 +13,6 @@ const Tag = Node.Tag;
 
 const CallingConvention = std.builtin.CallingConvention;
 
-pub const ClangErrMsg = clang.Stage2ErrorMsg;
-
 pub const Error = std.mem.Allocator.Error;
 const MacroProcessingError = Error || error{UnexpectedMacroToken};
 const TypeError = Error || error{UnsupportedType};
@@ -350,7 +348,7 @@ pub fn translate(
     gpa: mem.Allocator,
     args_begin: [*]?[*]const u8,
     args_end: [*]?[*]const u8,
-    errors: *[]ClangErrMsg,
+    errors: *[]clang.ErrorMsg,
     resources_path: [*:0]const u8,
 ) !std.zig.Ast {
     // TODO stage2 bug
@@ -5115,10 +5113,6 @@ pub fn failDecl(c: *Context, loc: clang.SourceLocation, name: []const u8, compti
     try c.global_scope.nodes.append(try Tag.warning.create(c.arena, location_comment));
 }
 
-pub fn freeErrors(errors: []ClangErrMsg) void {
-    errors.ptr.delete(errors.len);
-}
-
 const PatternList = struct {
     patterns: []Pattern,
 
test/cases/compile_errors/stage1/obj/cImport_with_bogus_include.zig → test/cases/compile_errors/cImport_with_bogus_include.zig
@@ -2,8 +2,8 @@ const c = @cImport(@cInclude("bogus.h"));
 export fn entry() usize { return @sizeOf(@TypeOf(c.bogo)); }
 
 // error
-// backend=stage1
+// backend=llvm
 // target=native
 //
-// tmp.zig:1:11: error: C import failed
-// .h:1:10: note: 'bogus.h' file not found
+// :1:11: error: C import failed
+// :1:10: error: 'bogus.h' file not found