self-hosted: fix error messages not cleaning up correctly

This commit is contained in:
Andrew Kelley 2018-07-23 14:28:14 -04:00
parent d767fae47e
commit 10d2f08d37
6 changed files with 251 additions and 108 deletions

View File

@ -24,6 +24,7 @@ const Visib = @import("visib.zig").Visib;
const Value = @import("value.zig").Value;
const Type = Value.Type;
const Span = errmsg.Span;
const Msg = errmsg.Msg;
const codegen = @import("codegen.zig");
const Package = @import("package.zig").Package;
const link = @import("link.zig").link;
@ -40,8 +41,6 @@ pub const EventLoopLocal = struct {
native_libc: event.Future(LibCInstallation),
cwd: []const u8,
var lazy_init_targets = std.lazyInit(void);
fn init(loop: *event.Loop) !EventLoopLocal {
@ -54,21 +53,16 @@ pub const EventLoopLocal = struct {
try std.os.getRandomBytes(seed_bytes[0..]);
const seed = std.mem.readInt(seed_bytes, u64, builtin.Endian.Big);
const cwd = try os.getCwd(loop.allocator);
errdefer loop.allocator.free(cwd);
return EventLoopLocal{
.loop = loop,
.llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(),
.prng = event.Locked(std.rand.DefaultPrng).init(loop, std.rand.DefaultPrng.init(seed)),
.native_libc = event.Future(LibCInstallation).init(loop),
.cwd = cwd,
};
}
/// Must be called only after EventLoop.run completes.
fn deinit(self: *EventLoopLocal) void {
self.loop.allocator.free(self.cwd);
while (self.llvm_handle_pool.pop()) |node| {
c.LLVMContextDispose(node.data);
self.loop.allocator.destroy(node);
@ -234,7 +228,7 @@ pub const Compilation = struct {
const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql);
const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8);
const CompileErrList = std.ArrayList(*errmsg.Msg);
const CompileErrList = std.ArrayList(*Msg);
// TODO handle some of these earlier and report them in a way other than error codes
pub const BuildError = error{
@ -286,7 +280,7 @@ pub const Compilation = struct {
pub const Event = union(enum) {
Ok,
Error: BuildError,
Fail: []*errmsg.Msg,
Fail: []*Msg,
};
pub const DarwinVersionMin = union(enum) {
@ -761,21 +755,35 @@ pub const Compilation = struct {
};
errdefer self.gpa().free(source_code);
var tree = try std.zig.parse(self.gpa(), source_code);
errdefer tree.deinit();
const tree = try self.gpa().createOne(ast.Tree);
tree.* = try std.zig.parse(self.gpa(), source_code);
errdefer {
tree.deinit();
self.gpa().destroy(tree);
}
break :blk try Scope.Root.create(self, tree, root_src_real_path);
};
defer root_scope.base.deref(self);
const tree = root_scope.tree;
const tree = &root_scope.tree;
var error_it = tree.errors.iterator(0);
while (error_it.next()) |parse_error| {
const msg = try Msg.createFromParseErrorAndScope(self, root_scope, parse_error);
errdefer msg.destroy();
try await (async self.addCompileErrorAsync(msg) catch unreachable);
}
if (tree.errors.len != 0) {
return;
}
const decls = try Scope.Decls.create(self, &root_scope.base);
defer decls.base.deref(self);
var decl_group = event.Group(BuildError!void).init(self.loop);
// TODO https://github.com/ziglang/zig/issues/1261
//errdefer decl_group.cancelAll();
var decl_group_consumed = false;
errdefer if (!decl_group_consumed) decl_group.cancelAll();
var it = tree.root_node.decls.iterator(0);
while (it.next()) |decl_ptr| {
@ -817,6 +825,7 @@ pub const Compilation = struct {
else => unreachable,
}
}
decl_group_consumed = true;
try await (async decl_group.wait() catch unreachable);
// Now other code can rely on the decls scope having a complete list of names.
@ -897,7 +906,7 @@ pub const Compilation = struct {
}
async fn addTopLevelDecl(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void {
const tree = &decl.findRootScope().tree;
const tree = decl.findRootScope().tree;
const is_export = decl.isExported(tree);
var add_to_table_resolved = false;
@ -927,25 +936,17 @@ pub const Compilation = struct {
const text = try std.fmt.allocPrint(self.gpa(), fmt, args);
errdefer self.gpa().free(text);
try self.prelink_group.call(addCompileErrorAsync, self, root, span, text);
const msg = try Msg.createFromScope(self, root, span, text);
errdefer msg.destroy();
try self.prelink_group.call(addCompileErrorAsync, self, msg);
}
async fn addCompileErrorAsync(
self: *Compilation,
root: *Scope.Root,
span: Span,
text: []u8,
msg: *Msg,
) !void {
const relpath = try os.path.relative(self.gpa(), self.event_loop_local.cwd, root.realpath);
errdefer self.gpa().free(relpath);
const msg = try self.gpa().create(errmsg.Msg{
.path = if (relpath.len < root.realpath.len) relpath else root.realpath,
.text = text,
.span = span,
.tree = &root.tree,
});
errdefer self.gpa().destroy(msg);
errdefer msg.destroy();
const compile_errors = await (async self.compile_errors.acquire() catch unreachable);
defer compile_errors.release();

View File

@ -4,6 +4,8 @@ const os = std.os;
const Token = std.zig.Token;
const ast = std.zig.ast;
const TokenIndex = std.zig.ast.TokenIndex;
const Compilation = @import("compilation.zig").Compilation;
const Scope = @import("scope.zig").Scope;
pub const Color = enum {
Auto,
@ -31,20 +33,129 @@ pub const Span = struct {
};
pub const Msg = struct {
path: []const u8,
text: []u8,
span: Span,
tree: *ast.Tree,
text: []u8,
data: Data,
const Data = union(enum) {
PathAndTree: PathAndTree,
ScopeAndComp: ScopeAndComp,
};
/// `path` must outlive the returned Msg
const PathAndTree = struct {
realpath: []const u8,
tree: *ast.Tree,
allocator: *mem.Allocator,
};
const ScopeAndComp = struct {
root_scope: *Scope.Root,
compilation: *Compilation,
};
pub fn destroy(self: *Msg) void {
switch (self.data) {
Data.PathAndTree => |path_and_tree| {
path_and_tree.allocator.free(self.text);
path_and_tree.allocator.destroy(self);
},
Data.ScopeAndComp => |scope_and_comp| {
scope_and_comp.root_scope.base.deref(scope_and_comp.compilation);
scope_and_comp.compilation.gpa().free(self.text);
scope_and_comp.compilation.gpa().destroy(self);
},
}
}
fn getAllocator(self: *const Msg) *mem.Allocator {
switch (self.data) {
Data.PathAndTree => |path_and_tree| {
return path_and_tree.allocator;
},
Data.ScopeAndComp => |scope_and_comp| {
return scope_and_comp.compilation.gpa();
},
}
}
pub fn getRealPath(self: *const Msg) []const u8 {
switch (self.data) {
Data.PathAndTree => |path_and_tree| {
return path_and_tree.realpath;
},
Data.ScopeAndComp => |scope_and_comp| {
return scope_and_comp.root_scope.realpath;
},
}
}
pub fn getTree(self: *const Msg) *ast.Tree {
switch (self.data) {
Data.PathAndTree => |path_and_tree| {
return path_and_tree.tree;
},
Data.ScopeAndComp => |scope_and_comp| {
return scope_and_comp.root_scope.tree;
},
}
}
/// Takes ownership of text
/// References root_scope, and derefs when the msg is freed
pub fn createFromScope(comp: *Compilation, root_scope: *Scope.Root, span: Span, text: []u8) !*Msg {
const msg = try comp.gpa().create(Msg{
.text = text,
.span = span,
.data = Data{
.ScopeAndComp = ScopeAndComp{
.root_scope = root_scope,
.compilation = comp,
},
},
});
root_scope.base.ref();
return msg;
}
pub fn createFromParseErrorAndScope(
comp: *Compilation,
root_scope: *Scope.Root,
parse_error: *const ast.Error,
) !*Msg {
const loc_token = parse_error.loc();
var text_buf = try std.Buffer.initSize(comp.gpa(), 0);
defer text_buf.deinit();
var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
try parse_error.render(&root_scope.tree.tokens, out_stream);
const msg = try comp.gpa().create(Msg{
.text = undefined,
.span = Span{
.first = loc_token,
.last = loc_token,
},
.data = Data{
.ScopeAndComp = ScopeAndComp{
.root_scope = root_scope,
.compilation = comp,
},
},
});
root_scope.base.ref();
msg.text = text_buf.toOwnedSlice();
return msg;
}
/// `realpath` must outlive the returned Msg
/// `tree` must outlive the returned Msg
/// Caller owns returned Msg and must free with `allocator`
/// allocator will additionally be used for printing messages later.
pub fn createFromParseError(
allocator: *mem.Allocator,
parse_error: *const ast.Error,
tree: *ast.Tree,
path: []const u8,
realpath: []const u8,
) !*Msg {
const loc_token = parse_error.loc();
var text_buf = try std.Buffer.initSize(allocator, 0);
@ -54,28 +165,46 @@ pub fn createFromParseError(
try parse_error.render(&tree.tokens, out_stream);
const msg = try allocator.create(Msg{
.text = undefined,
.data = Data{
.PathAndTree = PathAndTree{
.allocator = allocator,
.realpath = realpath,
.tree = tree,
.path = path,
.text = text_buf.toOwnedSlice(),
},
},
.span = Span{
.first = loc_token,
.last = loc_token,
},
});
msg.text = text_buf.toOwnedSlice();
errdefer allocator.destroy(msg);
return msg;
}
pub fn printToStream(stream: var, msg: *const Msg, color_on: bool) !void {
const first_token = msg.tree.tokens.at(msg.span.first);
const last_token = msg.tree.tokens.at(msg.span.last);
const start_loc = msg.tree.tokenLocationPtr(0, first_token);
const end_loc = msg.tree.tokenLocationPtr(first_token.end, last_token);
pub fn printToStream(msg: *const Msg, stream: var, color_on: bool) !void {
const allocator = msg.getAllocator();
const realpath = msg.getRealPath();
const tree = msg.getTree();
const cwd = try os.getCwd(allocator);
defer allocator.free(cwd);
const relpath = try os.path.relative(allocator, cwd, realpath);
defer allocator.free(relpath);
const path = if (relpath.len < realpath.len) relpath else realpath;
const first_token = tree.tokens.at(msg.span.first);
const last_token = tree.tokens.at(msg.span.last);
const start_loc = tree.tokenLocationPtr(0, first_token);
const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
if (!color_on) {
try stream.print(
"{}:{}:{}: error: {}\n",
msg.path,
path,
start_loc.line + 1,
start_loc.column + 1,
msg.text,
@ -85,23 +214,24 @@ pub fn printToStream(stream: var, msg: *const Msg, color_on: bool) !void {
try stream.print(
"{}:{}:{}: error: {}\n{}\n",
msg.path,
path,
start_loc.line + 1,
start_loc.column + 1,
msg.text,
msg.tree.source[start_loc.line_start..start_loc.line_end],
tree.source[start_loc.line_start..start_loc.line_end],
);
try stream.writeByteNTimes(' ', start_loc.column);
try stream.writeByteNTimes('~', last_token.end - first_token.start);
try stream.write("\n");
}
pub fn printToFile(file: *os.File, msg: *const Msg, color: Color) !void {
pub fn printToFile(msg: *const Msg, file: *os.File, color: Color) !void {
const color_on = switch (color) {
Color.Auto => file.isTty(),
Color.On => true,
Color.Off => false,
};
var stream = &std.io.FileOutStream.init(file).stream;
return printToStream(stream, msg, color_on);
return msg.printToStream(stream, color_on);
}
};

View File

@ -485,7 +485,8 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void {
},
Compilation.Event.Fail => |msgs| {
for (msgs) |msg| {
errmsg.printToFile(&stderr_file, msg, color) catch os.exit(1);
defer msg.destroy();
msg.printToFile(&stderr_file, color) catch os.exit(1);
}
},
}
@ -646,10 +647,10 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
var error_it = tree.errors.iterator(0);
while (error_it.next()) |parse_error| {
const msg = try errmsg.createFromParseError(allocator, parse_error, &tree, "<stdin>");
defer allocator.destroy(msg);
const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, &tree, "<stdin>");
defer msg.destroy();
try errmsg.printToFile(&stderr_file, msg, color);
try msg.printToFile(&stderr_file, color);
}
if (tree.errors.len != 0) {
os.exit(1);
@ -702,10 +703,10 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
var error_it = tree.errors.iterator(0);
while (error_it.next()) |parse_error| {
const msg = try errmsg.createFromParseError(allocator, parse_error, &tree, file_path);
defer allocator.destroy(msg);
const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, &tree, file_path);
defer msg.destroy();
try errmsg.printToFile(&stderr_file, msg, color);
try msg.printToFile(&stderr_file, color);
}
if (tree.errors.len != 0) {
fmt.any_error = true;

View File

@ -93,13 +93,13 @@ pub const Scope = struct {
pub const Root = struct {
base: Scope,
tree: ast.Tree,
tree: *ast.Tree,
realpath: []const u8,
/// Creates a Root scope with 1 reference
/// Takes ownership of realpath
/// Caller must set tree
pub fn create(comp: *Compilation, tree: ast.Tree, realpath: []u8) !*Root {
/// Takes ownership of tree, will deinit and destroy when done.
pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root {
const self = try comp.gpa().create(Root{
.base = Scope{
.id = Id.Root,
@ -117,6 +117,7 @@ pub const Scope = struct {
pub fn destroy(self: *Root, comp: *Compilation) void {
comp.gpa().free(self.tree.source);
self.tree.deinit();
comp.gpa().destroy(self.tree);
comp.gpa().free(self.realpath);
comp.gpa().destroy(self);
}

View File

@ -184,7 +184,8 @@ pub const TestContext = struct {
var stderr = try std.io.getStdErr();
try stderr.write("build incorrectly failed:\n");
for (msgs) |msg| {
try errmsg.printToFile(&stderr, msg, errmsg.Color.Auto);
defer msg.destroy();
try msg.printToFile(&stderr, errmsg.Color.Auto);
}
},
}
@ -211,10 +212,10 @@ pub const TestContext = struct {
Compilation.Event.Fail => |msgs| {
assertOrPanic(msgs.len != 0);
for (msgs) |msg| {
if (mem.endsWith(u8, msg.path, path) and mem.eql(u8, msg.text, text)) {
const first_token = msg.tree.tokens.at(msg.span.first);
const last_token = msg.tree.tokens.at(msg.span.first);
const start_loc = msg.tree.tokenLocationPtr(0, first_token);
if (mem.endsWith(u8, msg.getRealPath(), path) and mem.eql(u8, msg.text, text)) {
const first_token = msg.getTree().tokens.at(msg.span.first);
const last_token = msg.getTree().tokens.at(msg.span.first);
const start_loc = msg.getTree().tokenLocationPtr(0, first_token);
if (start_loc.line + 1 == line and start_loc.column + 1 == column) {
return;
}
@ -231,7 +232,8 @@ pub const TestContext = struct {
std.debug.warn("\n====found:========\n");
var stderr = try std.io.getStdErr();
for (msgs) |msg| {
try errmsg.printToFile(&stderr, msg, errmsg.Color.Auto);
defer msg.destroy();
try msg.printToFile(&stderr, errmsg.Color.Auto);
}
std.debug.warn("============\n");
return error.TestFailed;

View File

@ -35,6 +35,7 @@ pub const Allocator = struct {
freeFn: fn (self: *Allocator, old_mem: []u8) void,
/// Call `destroy` with the result
/// TODO this is deprecated. use createOne instead
pub fn create(self: *Allocator, init: var) Error!*@typeOf(init) {
const T = @typeOf(init);
if (@sizeOf(T) == 0) return &(T{});
@ -44,6 +45,14 @@ pub const Allocator = struct {
return ptr;
}
/// Call `destroy` with the result.
/// Returns undefined memory.
pub fn createOne(self: *Allocator, comptime T: type) Error!*T {
if (@sizeOf(T) == 0) return &(T{});
const slice = try self.alloc(T, 1);
return &slice[0];
}
/// `ptr` should be the return value of `create`
pub fn destroy(self: *Allocator, ptr: var) void {
const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr));
@ -155,7 +164,6 @@ pub fn copyBackwards(comptime T: type, dest: []T, source: []const T) void {
}
}
pub fn set(comptime T: type, dest: []T, value: T) void {
for (dest) |*d|
d.* = value;