mirror of
https://github.com/ziglang/zig.git
synced 2026-02-07 23:17:17 +00:00
Merge remote-tracking branch 'origin/master' into llvm15
This commit is contained in:
commit
ba70eee8bb
3
.github/CODEOWNERS
vendored
Normal file
3
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Autodoc
|
||||
/src/Autodoc.zig @kristoff-it
|
||||
/lib/docs/* @kristoff-it
|
||||
12
.github/ISSUE_TEMPLATE/autodoc-issue.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/autodoc-issue.md
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
name: Autodoc Issue
|
||||
about: Issues with automatically generated docs, including stdlib docs.
|
||||
title: 'Autodoc: {your issue}'
|
||||
labels: autodoc
|
||||
assignees: kristoff-it
|
||||
|
||||
---
|
||||
|
||||
Autodoc is still work in progress and as such many bugs and missing features are already known.
|
||||
|
||||
# Please report only <ins>regressions</ins>, i.e. things that worked in a previous build of new Autodoc (orange banner) that now don't work any more.
|
||||
@ -284,6 +284,16 @@
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.fieldHasDocs {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.fieldDocs {
|
||||
border: 1px solid #2A2A2A;
|
||||
border-top: 0px;
|
||||
padding: 1px 1em;
|
||||
}
|
||||
|
||||
/* help dialog */
|
||||
.help-modal {
|
||||
display: flex;
|
||||
|
||||
@ -618,15 +618,18 @@ var zigAnalysis;
|
||||
for (let i = 0; i < fields.length; i += 1) {
|
||||
let field = fields[i];
|
||||
let fieldNode = zigAnalysis.astNodes[field];
|
||||
let docs = fieldNode.docs;
|
||||
if (fieldNode.docs == null) {
|
||||
continue;
|
||||
}
|
||||
let docsNonEmpty = docs !== "";
|
||||
let divDom = domListParams.children[domIndex];
|
||||
domIndex += 1;
|
||||
|
||||
|
||||
let value = typeObj.params[i];
|
||||
let html = '<pre>' + escapeHtml((fieldNode.name)) + ": ";
|
||||
let preClass = docsNonEmpty ? ' class="fieldHasDocs"' : "";
|
||||
let html = '<pre' + preClass + '>' + escapeHtml((fieldNode.name)) + ": ";
|
||||
if (isVarArgs && i === typeObj.params.length - 1) {
|
||||
html += '...';
|
||||
} else {
|
||||
@ -636,9 +639,8 @@ var zigAnalysis;
|
||||
|
||||
html += ',</pre>';
|
||||
|
||||
let docs = fieldNode.docs;
|
||||
if (docs != null) {
|
||||
html += markdown(docs);
|
||||
if (docsNonEmpty) {
|
||||
html += '<div class="fieldDocs">' + markdown(docs) + '</div>';
|
||||
}
|
||||
divDom.innerHTML = html;
|
||||
}
|
||||
@ -824,7 +826,13 @@ var zigAnalysis;
|
||||
|
||||
function exprName(expr, opts) {
|
||||
switch (Object.keys(expr)[0]) {
|
||||
default: throw "oh no";
|
||||
default: throw "this expression is not implemented yet";
|
||||
case "bool": {
|
||||
if (expr.bool) {
|
||||
return "true";
|
||||
}
|
||||
return "false";
|
||||
}
|
||||
case "&": {
|
||||
return "&" + exprName(zigAnalysis.exprs[expr["&"]]);
|
||||
}
|
||||
@ -929,8 +937,7 @@ var zigAnalysis;
|
||||
return exprName(switchIndex, opts);
|
||||
}
|
||||
case "refPath" : {
|
||||
const declRef = expr.refPath[0].declRef;
|
||||
let name = zigAnalysis.decls[declRef].name;
|
||||
let name = exprName(expr.refPath[0]);
|
||||
for (let i = 1; i < expr.refPath.length; i++) {
|
||||
let component = undefined;
|
||||
if ("string" in expr.refPath[i]) {
|
||||
@ -1402,6 +1409,11 @@ var zigAnalysis;
|
||||
let payloadHtml = "@TypeOf(" + exprName(typeRefArg, {wantHtml: true, wantLink:true}) + ")";
|
||||
return payloadHtml;
|
||||
}
|
||||
case "typeInfo": {
|
||||
const typeRefArg = zigAnalysis.exprs[expr.typeInfo];
|
||||
let payloadHtml = "@typeInfo(" + exprName(typeRefArg, {wantHtml: true, wantLink:true}) + ")";
|
||||
return payloadHtml;
|
||||
}
|
||||
case "null": {
|
||||
return "null";
|
||||
}
|
||||
@ -1693,6 +1705,12 @@ var zigAnalysis;
|
||||
let rhs = exprName(errUnionObj.rhs, opts);
|
||||
return lhs + "!" + rhs;
|
||||
}
|
||||
case typeKinds.InferredErrorUnion:
|
||||
{
|
||||
let errUnionObj = (typeObj);
|
||||
let payload = exprName(errUnionObj.payload, opts);
|
||||
return "!" + payload;
|
||||
}
|
||||
case typeKinds.Fn:
|
||||
{
|
||||
let fnObj = (typeObj);
|
||||
@ -2270,8 +2288,11 @@ var zigAnalysis;
|
||||
let fieldNode = zigAnalysis.astNodes[containerNode.fields[i]];
|
||||
let divDom = domListFields.children[i];
|
||||
let fieldName = (fieldNode.name);
|
||||
let docs = fieldNode.docs;
|
||||
let docsNonEmpty = docs != null && docs !== "";
|
||||
let extraPreClass = docsNonEmpty ? " fieldHasDocs" : "";
|
||||
|
||||
let html = '<div class="mobile-scroll-container"><pre class="scroll-item">' + escapeHtml(fieldName);
|
||||
let html = '<div class="mobile-scroll-container"><pre class="scroll-item' + extraPreClass + '">' + escapeHtml(fieldName);
|
||||
|
||||
if (container.kind === typeKinds.Enum) {
|
||||
html += ' = <span class="tok-number">' + fieldName + '</span>';
|
||||
@ -2289,9 +2310,8 @@ var zigAnalysis;
|
||||
|
||||
html += ',</pre></div>';
|
||||
|
||||
let docs = fieldNode.docs;
|
||||
if (docs != null) {
|
||||
html += markdown(docs);
|
||||
if (docsNonEmpty) {
|
||||
html += '<div class="fieldDocs">' + markdown(docs) + '</div>';
|
||||
}
|
||||
divDom.innerHTML = html;
|
||||
}
|
||||
@ -2639,14 +2659,23 @@ var zigAnalysis;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function shortDescMarkdown(docs) {
|
||||
let parts = docs.trim().split("\n");
|
||||
let firstLine = parts[0];
|
||||
return markdown(firstLine);
|
||||
const trimmed_docs = docs.trim();
|
||||
let index = trimmed_docs.indexOf('.');
|
||||
if (index < 0) {
|
||||
index = trimmed_docs.indexOf('\n');
|
||||
if (index < 0) {
|
||||
index = trimmed_docs.length;
|
||||
}
|
||||
} else {
|
||||
index += 1; // include the period
|
||||
}
|
||||
const slice = trimmed_docs.slice(0, index);
|
||||
return markdown(slice);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function markdown(input) {
|
||||
const raw_lines = input.split('\n'); // zig allows no '\r', so we don't need to split on CR
|
||||
|
||||
|
||||
@ -1,11 +1,24 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn main() anyerror!void {
|
||||
// Note that info level log messages are by default printed only in Debug
|
||||
// and ReleaseSafe build modes.
|
||||
std.log.info("All your codebase are belong to us.", .{});
|
||||
pub fn main() !void {
|
||||
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
|
||||
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
|
||||
|
||||
// stdout is for the actual output of your application, for example if you
|
||||
// are implementing gzip, then only the compressed bytes should be sent to
|
||||
// stdout, not any debugging messages.
|
||||
const stdout_file = std.io.getStdOut().writer();
|
||||
var bw = std.io.bufferedWriter(stdout_file);
|
||||
const stdout = bw.writer();
|
||||
|
||||
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
||||
|
||||
try bw.flush(); // don't forget to flush!
|
||||
}
|
||||
|
||||
test "basic test" {
|
||||
try std.testing.expectEqual(10, 3 + 7);
|
||||
test "simple test" {
|
||||
var list = std.ArrayList(i32).init(std.testing.allocator);
|
||||
defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
|
||||
try list.append(42);
|
||||
try std.testing.expectEqual(@as(i32, 42), list.pop());
|
||||
}
|
||||
|
||||
@ -283,7 +283,14 @@ fn make(step: *Step) !void {
|
||||
|
||||
const gpa = self.builder.allocator;
|
||||
const src_path = self.source.getPath(self.builder);
|
||||
const contents = try fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
|
||||
const contents = try fs.cwd().readFileAllocOptions(
|
||||
gpa,
|
||||
src_path,
|
||||
self.max_bytes,
|
||||
null,
|
||||
@alignOf(u64),
|
||||
null,
|
||||
);
|
||||
|
||||
const output = switch (self.obj_format) {
|
||||
.macho => try MachODumper.parseAndDump(contents, .{
|
||||
@ -370,9 +377,10 @@ const Opts = struct {
|
||||
};
|
||||
|
||||
const MachODumper = struct {
|
||||
const LoadCommandIterator = macho.LoadCommandIterator;
|
||||
const symtab_label = "symtab";
|
||||
|
||||
fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 {
|
||||
fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
|
||||
const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator
|
||||
var stream = std.io.fixedBufferStream(bytes);
|
||||
const reader = stream.reader();
|
||||
@ -385,55 +393,54 @@ const MachODumper = struct {
|
||||
var output = std.ArrayList(u8).init(gpa);
|
||||
const writer = output.writer();
|
||||
|
||||
var load_commands = std.ArrayList(macho.LoadCommand).init(gpa);
|
||||
try load_commands.ensureTotalCapacity(hdr.ncmds);
|
||||
|
||||
var sections = std.ArrayList(struct { seg: u16, sect: u16 }).init(gpa);
|
||||
var imports = std.ArrayList(u16).init(gpa);
|
||||
|
||||
var symtab_cmd: ?u16 = null;
|
||||
var i: u16 = 0;
|
||||
while (i < hdr.ncmds) : (i += 1) {
|
||||
var cmd = try macho.LoadCommand.read(gpa, reader);
|
||||
load_commands.appendAssumeCapacity(cmd);
|
||||
var symtab: []const macho.nlist_64 = undefined;
|
||||
var strtab: []const u8 = undefined;
|
||||
var sections = std.ArrayList(macho.section_64).init(gpa);
|
||||
var imports = std.ArrayList([]const u8).init(gpa);
|
||||
|
||||
var it = LoadCommandIterator{
|
||||
.ncmds = hdr.ncmds,
|
||||
.buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
|
||||
};
|
||||
var i: usize = 0;
|
||||
while (it.next()) |cmd| {
|
||||
switch (cmd.cmd()) {
|
||||
.SEGMENT_64 => {
|
||||
const seg = cmd.segment;
|
||||
for (seg.sections.items) |_, j| {
|
||||
try sections.append(.{ .seg = i, .sect = @intCast(u16, j) });
|
||||
const seg = cmd.cast(macho.segment_command_64).?;
|
||||
try sections.ensureUnusedCapacity(seg.nsects);
|
||||
for (cmd.getSections()) |sect| {
|
||||
sections.appendAssumeCapacity(sect);
|
||||
}
|
||||
},
|
||||
.SYMTAB => {
|
||||
symtab_cmd = i;
|
||||
.SYMTAB => if (opts.dump_symtab) {
|
||||
const lc = cmd.cast(macho.symtab_command).?;
|
||||
symtab = @ptrCast(
|
||||
[*]const macho.nlist_64,
|
||||
@alignCast(@alignOf(macho.nlist_64), &bytes[lc.symoff]),
|
||||
)[0..lc.nsyms];
|
||||
strtab = bytes[lc.stroff..][0..lc.strsize];
|
||||
},
|
||||
.LOAD_DYLIB,
|
||||
.LOAD_WEAK_DYLIB,
|
||||
.REEXPORT_DYLIB,
|
||||
=> {
|
||||
try imports.append(i);
|
||||
try imports.append(cmd.getDylibPathName());
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
try dumpLoadCommand(cmd, i, writer);
|
||||
try writer.writeByte('\n');
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (opts.dump_symtab) {
|
||||
const cmd = load_commands.items[symtab_cmd.?].symtab;
|
||||
try writer.writeAll(symtab_label ++ "\n");
|
||||
const strtab = bytes[cmd.stroff..][0..cmd.strsize];
|
||||
const raw_symtab = bytes[cmd.symoff..][0 .. cmd.nsyms * @sizeOf(macho.nlist_64)];
|
||||
const symtab = mem.bytesAsSlice(macho.nlist_64, raw_symtab);
|
||||
|
||||
for (symtab) |sym| {
|
||||
if (sym.stab()) continue;
|
||||
const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0);
|
||||
if (sym.sect()) {
|
||||
const map = sections.items[sym.n_sect - 1];
|
||||
const seg = load_commands.items[map.seg].segment;
|
||||
const sect = seg.sections.items[map.sect];
|
||||
const sect = sections.items[sym.n_sect - 1];
|
||||
try writer.print("{x} ({s},{s})", .{
|
||||
sym.n_value,
|
||||
sect.segName(),
|
||||
@ -455,9 +462,7 @@ const MachODumper = struct {
|
||||
break :blk "flat lookup";
|
||||
unreachable;
|
||||
}
|
||||
const import_id = imports.items[@bitCast(u16, ordinal) - 1];
|
||||
const import = load_commands.items[import_id].dylib;
|
||||
const full_path = mem.sliceTo(import.data, 0);
|
||||
const full_path = imports.items[@bitCast(u16, ordinal) - 1];
|
||||
const basename = fs.path.basename(full_path);
|
||||
assert(basename.len > 0);
|
||||
const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len;
|
||||
@ -481,7 +486,7 @@ const MachODumper = struct {
|
||||
return output.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void {
|
||||
fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void {
|
||||
// print header first
|
||||
try writer.print(
|
||||
\\LC {d}
|
||||
@ -491,8 +496,7 @@ const MachODumper = struct {
|
||||
|
||||
switch (lc.cmd()) {
|
||||
.SEGMENT_64 => {
|
||||
// TODO dump section headers
|
||||
const seg = lc.segment.inner;
|
||||
const seg = lc.cast(macho.segment_command_64).?;
|
||||
try writer.writeByte('\n');
|
||||
try writer.print(
|
||||
\\segname {s}
|
||||
@ -508,7 +512,7 @@ const MachODumper = struct {
|
||||
seg.filesize,
|
||||
});
|
||||
|
||||
for (lc.segment.sections.items) |sect| {
|
||||
for (lc.getSections()) |sect| {
|
||||
try writer.writeByte('\n');
|
||||
try writer.print(
|
||||
\\sectname {s}
|
||||
@ -531,7 +535,7 @@ const MachODumper = struct {
|
||||
.LOAD_WEAK_DYLIB,
|
||||
.REEXPORT_DYLIB,
|
||||
=> {
|
||||
const dylib = lc.dylib.inner.dylib;
|
||||
const dylib = lc.cast(macho.dylib_command).?;
|
||||
try writer.writeByte('\n');
|
||||
try writer.print(
|
||||
\\name {s}
|
||||
@ -539,19 +543,20 @@ const MachODumper = struct {
|
||||
\\current version {x}
|
||||
\\compatibility version {x}
|
||||
, .{
|
||||
mem.sliceTo(lc.dylib.data, 0),
|
||||
dylib.timestamp,
|
||||
dylib.current_version,
|
||||
dylib.compatibility_version,
|
||||
lc.getDylibPathName(),
|
||||
dylib.dylib.timestamp,
|
||||
dylib.dylib.current_version,
|
||||
dylib.dylib.compatibility_version,
|
||||
});
|
||||
},
|
||||
|
||||
.MAIN => {
|
||||
const main = lc.cast(macho.entry_point_command).?;
|
||||
try writer.writeByte('\n');
|
||||
try writer.print(
|
||||
\\entryoff {x}
|
||||
\\stacksize {x}
|
||||
, .{ lc.main.entryoff, lc.main.stacksize });
|
||||
, .{ main.entryoff, main.stacksize });
|
||||
},
|
||||
|
||||
.RPATH => {
|
||||
@ -559,7 +564,7 @@ const MachODumper = struct {
|
||||
try writer.print(
|
||||
\\path {s}
|
||||
, .{
|
||||
mem.sliceTo(lc.rpath.data, 0),
|
||||
lc.getRpathPathName(),
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -846,6 +846,17 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checkNonScalarSentinel(expected: anytype, actual: @TypeOf(expected)) void {
|
||||
if (!std.meta.eql(expected, actual)) {
|
||||
panicSentinelMismatch(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn panicSentinelMismatch(expected: anytype, actual: @TypeOf(expected)) noreturn {
|
||||
@setCold(true);
|
||||
std.debug.panic("sentinel mismatch: expected {any}, found {any}", .{ expected, actual });
|
||||
}
|
||||
|
||||
pub fn panicUnwrapError(st: ?*StackTrace, err: anyerror) noreturn {
|
||||
@setCold(true);
|
||||
std.debug.panicExtra(st, "attempt to unwrap error: {s}", .{@errorName(err)});
|
||||
|
||||
@ -229,15 +229,14 @@ pub const Ed25519 = struct {
|
||||
blind_secret_key: BlindSecretKey,
|
||||
};
|
||||
|
||||
/// Blind an existing key pair with a blinding seed.
|
||||
pub fn blind(key_pair: Ed25519.KeyPair, blind_seed: [blind_seed_length]u8) !BlindKeyPair {
|
||||
/// Blind an existing key pair with a blinding seed and a context.
|
||||
pub fn blind(key_pair: Ed25519.KeyPair, blind_seed: [blind_seed_length]u8, ctx: []const u8) !BlindKeyPair {
|
||||
var h: [Sha512.digest_length]u8 = undefined;
|
||||
Sha512.hash(key_pair.secret_key[0..32], &h, .{});
|
||||
Curve.scalar.clamp(h[0..32]);
|
||||
const scalar = Curve.scalar.reduce(h[0..32].*);
|
||||
|
||||
var blind_h: [Sha512.digest_length]u8 = undefined;
|
||||
Sha512.hash(blind_seed[0..], &blind_h, .{});
|
||||
const blind_h = blindCtx(blind_seed, ctx);
|
||||
const blind_factor = Curve.scalar.reduce(blind_h[0..32].*);
|
||||
|
||||
const blind_scalar = Curve.scalar.mul(scalar, blind_factor);
|
||||
@ -259,9 +258,8 @@ pub const Ed25519 = struct {
|
||||
}
|
||||
|
||||
/// Recover a public key from a blind version of it.
|
||||
pub fn unblindPublicKey(blind_public_key: [public_length]u8, blind_seed: [blind_seed_length]u8) ![public_length]u8 {
|
||||
var blind_h: [Sha512.digest_length]u8 = undefined;
|
||||
Sha512.hash(&blind_seed, &blind_h, .{});
|
||||
pub fn unblindPublicKey(blind_public_key: [public_length]u8, blind_seed: [blind_seed_length]u8, ctx: []const u8) ![public_length]u8 {
|
||||
const blind_h = blindCtx(blind_seed, ctx);
|
||||
const inv_blind_factor = Scalar.fromBytes(blind_h[0..32].*).invert().toBytes();
|
||||
const public_key = try (try Curve.fromBytes(blind_public_key)).mul(inv_blind_factor);
|
||||
return public_key.toBytes();
|
||||
@ -297,6 +295,17 @@ pub const Ed25519 = struct {
|
||||
mem.copy(u8, sig[32..], s[0..]);
|
||||
return sig;
|
||||
}
|
||||
|
||||
/// Compute a blind context from a blinding seed and a context.
|
||||
fn blindCtx(blind_seed: [blind_seed_length]u8, ctx: []const u8) [Sha512.digest_length]u8 {
|
||||
var blind_h: [Sha512.digest_length]u8 = undefined;
|
||||
var hx = Sha512.init(.{});
|
||||
hx.update(&blind_seed);
|
||||
hx.update(&[1]u8{0});
|
||||
hx.update(ctx);
|
||||
hx.final(&blind_h);
|
||||
return blind_h;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -458,7 +467,7 @@ test "ed25519 with blind keys" {
|
||||
crypto.random.bytes(&blind);
|
||||
|
||||
// Blind the key pair
|
||||
const blind_kp = try BlindKeySignatures.blind(kp, blind);
|
||||
const blind_kp = try BlindKeySignatures.blind(kp, blind, "ctx");
|
||||
|
||||
// Sign a message and check that it can be verified with the blind public key
|
||||
const msg = "test";
|
||||
@ -466,6 +475,6 @@ test "ed25519 with blind keys" {
|
||||
try Ed25519.verify(sig, msg, blind_kp.blind_public_key);
|
||||
|
||||
// Unblind the public key
|
||||
const pk = try BlindKeySignatures.unblindPublicKey(blind_kp.blind_public_key, blind);
|
||||
const pk = try BlindKeySignatures.unblindPublicKey(blind_kp.blind_public_key, blind, "ctx");
|
||||
try std.testing.expectEqualSlices(u8, &pk, &kp.public_key);
|
||||
}
|
||||
|
||||
@ -595,6 +595,19 @@ pub const IterableDir = struct {
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
||||
pub fn next(self: *Self) Error!?Entry {
|
||||
return self.nextLinux() catch |err| switch (err) {
|
||||
// To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
|
||||
// This matches the behavior of non-Linux UNIX platforms.
|
||||
error.DirNotFound => null,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const ErrorLinux = error{DirNotFound} || IteratorError;
|
||||
|
||||
/// Implementation of `next` that can return `error.DirNotFound` if the directory being
|
||||
/// iterated was deleted during iteration (this error is Linux specific).
|
||||
pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
|
||||
start_over: while (true) {
|
||||
if (self.index >= self.end_index) {
|
||||
if (self.first_iter) {
|
||||
@ -607,7 +620,7 @@ pub const IterableDir = struct {
|
||||
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.NOENT => return null, // The directory being iterated was deleted during iteration.
|
||||
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
|
||||
.INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
|
||||
else => |err| return os.unexpectedErrno(err),
|
||||
}
|
||||
@ -729,6 +742,20 @@ pub const IterableDir = struct {
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
||||
pub fn next(self: *Self) Error!?Entry {
|
||||
return self.nextWasi() catch |err| switch (err) {
|
||||
// To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
|
||||
// This matches the behavior of non-Linux UNIX platforms.
|
||||
error.DirNotFound => null,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const ErrorWasi = error{DirNotFound} || IteratorError;
|
||||
|
||||
/// Implementation of `next` that can return platform-dependent errors depending on the host platform.
|
||||
/// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
|
||||
/// iterated was deleted during iteration.
|
||||
pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
|
||||
// We intentinally use fd_readdir even when linked with libc,
|
||||
// since its implementation is exactly the same as below,
|
||||
// and we avoid the code complexity here.
|
||||
@ -742,7 +769,7 @@ pub const IterableDir = struct {
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.NOENT => return null, // The directory being iterated was deleted during iteration.
|
||||
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
else => |err| return os.unexpectedErrno(err),
|
||||
}
|
||||
|
||||
@ -241,6 +241,11 @@ test "Dir.Iterator but dir is deleted during iteration" {
|
||||
// Now, when we try to iterate, the next call should return null immediately.
|
||||
const entry = try iterator.next();
|
||||
try std.testing.expect(entry == null);
|
||||
|
||||
// On Linux, we can opt-in to receiving a more specific error by calling `nextLinux`
|
||||
if (builtin.os.tag == .linux) {
|
||||
try std.testing.expectError(error.DirNotFound, iterator.nextLinux());
|
||||
}
|
||||
}
|
||||
|
||||
fn entryEql(lhs: IterableDir.Entry, rhs: IterableDir.Entry) bool {
|
||||
|
||||
@ -780,7 +780,7 @@ pub const section_64 = extern struct {
|
||||
return parseName(§.segname);
|
||||
}
|
||||
|
||||
pub fn type_(sect: section_64) u8 {
|
||||
pub fn @"type"(sect: section_64) u8 {
|
||||
return @truncate(u8, sect.flags & 0xff);
|
||||
}
|
||||
|
||||
@ -793,6 +793,11 @@ pub const section_64 = extern struct {
|
||||
return attr & S_ATTR_PURE_INSTRUCTIONS != 0 or attr & S_ATTR_SOME_INSTRUCTIONS != 0;
|
||||
}
|
||||
|
||||
pub fn isZerofill(sect: section_64) bool {
|
||||
const tt = sect.@"type"();
|
||||
return tt == S_ZEROFILL or tt == S_GB_ZEROFILL or tt == S_THREAD_LOCAL_ZEROFILL;
|
||||
}
|
||||
|
||||
pub fn isDebug(sect: section_64) bool {
|
||||
return sect.attrs() & S_ATTR_DEBUG != 0;
|
||||
}
|
||||
@ -1835,429 +1840,70 @@ pub const data_in_code_entry = extern struct {
|
||||
kind: u16,
|
||||
};
|
||||
|
||||
/// A Zig wrapper for all known MachO load commands.
|
||||
/// Provides interface to read and write the load command data to a buffer.
|
||||
pub const LoadCommand = union(enum) {
|
||||
segment: SegmentCommand,
|
||||
dyld_info_only: dyld_info_command,
|
||||
symtab: symtab_command,
|
||||
dysymtab: dysymtab_command,
|
||||
dylinker: GenericCommandWithData(dylinker_command),
|
||||
dylib: GenericCommandWithData(dylib_command),
|
||||
main: entry_point_command,
|
||||
version_min: version_min_command,
|
||||
source_version: source_version_command,
|
||||
build_version: GenericCommandWithData(build_version_command),
|
||||
uuid: uuid_command,
|
||||
linkedit_data: linkedit_data_command,
|
||||
rpath: GenericCommandWithData(rpath_command),
|
||||
unknown: GenericCommandWithData(load_command),
|
||||
pub const LoadCommandIterator = struct {
|
||||
ncmds: usize,
|
||||
buffer: []align(@alignOf(u64)) const u8,
|
||||
index: usize = 0,
|
||||
|
||||
pub fn read(allocator: Allocator, reader: anytype) !LoadCommand {
|
||||
const header = try reader.readStruct(load_command);
|
||||
var buffer = try allocator.alloc(u8, header.cmdsize);
|
||||
defer allocator.free(buffer);
|
||||
mem.copy(u8, buffer, mem.asBytes(&header));
|
||||
try reader.readNoEof(buffer[@sizeOf(load_command)..]);
|
||||
var stream = io.fixedBufferStream(buffer);
|
||||
pub const LoadCommand = struct {
|
||||
hdr: load_command,
|
||||
data: []const u8,
|
||||
|
||||
return switch (header.cmd) {
|
||||
.SEGMENT_64 => LoadCommand{
|
||||
.segment = try SegmentCommand.read(allocator, stream.reader()),
|
||||
},
|
||||
.DYLD_INFO, .DYLD_INFO_ONLY => LoadCommand{
|
||||
.dyld_info_only = try stream.reader().readStruct(dyld_info_command),
|
||||
},
|
||||
.SYMTAB => LoadCommand{
|
||||
.symtab = try stream.reader().readStruct(symtab_command),
|
||||
},
|
||||
.DYSYMTAB => LoadCommand{
|
||||
.dysymtab = try stream.reader().readStruct(dysymtab_command),
|
||||
},
|
||||
.ID_DYLINKER, .LOAD_DYLINKER, .DYLD_ENVIRONMENT => LoadCommand{
|
||||
.dylinker = try GenericCommandWithData(dylinker_command).read(allocator, stream.reader()),
|
||||
},
|
||||
.ID_DYLIB, .LOAD_WEAK_DYLIB, .LOAD_DYLIB, .REEXPORT_DYLIB => LoadCommand{
|
||||
.dylib = try GenericCommandWithData(dylib_command).read(allocator, stream.reader()),
|
||||
},
|
||||
.MAIN => LoadCommand{
|
||||
.main = try stream.reader().readStruct(entry_point_command),
|
||||
},
|
||||
.VERSION_MIN_MACOSX, .VERSION_MIN_IPHONEOS, .VERSION_MIN_WATCHOS, .VERSION_MIN_TVOS => LoadCommand{
|
||||
.version_min = try stream.reader().readStruct(version_min_command),
|
||||
},
|
||||
.SOURCE_VERSION => LoadCommand{
|
||||
.source_version = try stream.reader().readStruct(source_version_command),
|
||||
},
|
||||
.BUILD_VERSION => LoadCommand{
|
||||
.build_version = try GenericCommandWithData(build_version_command).read(allocator, stream.reader()),
|
||||
},
|
||||
.UUID => LoadCommand{
|
||||
.uuid = try stream.reader().readStruct(uuid_command),
|
||||
},
|
||||
.FUNCTION_STARTS, .DATA_IN_CODE, .CODE_SIGNATURE => LoadCommand{
|
||||
.linkedit_data = try stream.reader().readStruct(linkedit_data_command),
|
||||
},
|
||||
.RPATH => LoadCommand{
|
||||
.rpath = try GenericCommandWithData(rpath_command).read(allocator, stream.reader()),
|
||||
},
|
||||
else => LoadCommand{
|
||||
.unknown = try GenericCommandWithData(load_command).read(allocator, stream.reader()),
|
||||
},
|
||||
pub fn cmd(lc: LoadCommand) LC {
|
||||
return lc.hdr.cmd;
|
||||
}
|
||||
|
||||
pub fn cmdsize(lc: LoadCommand) u32 {
|
||||
return lc.hdr.cmdsize;
|
||||
}
|
||||
|
||||
pub fn cast(lc: LoadCommand, comptime Cmd: type) ?Cmd {
|
||||
if (lc.data.len < @sizeOf(Cmd)) return null;
|
||||
return @ptrCast(*const Cmd, @alignCast(@alignOf(Cmd), &lc.data[0])).*;
|
||||
}
|
||||
|
||||
/// Asserts LoadCommand is of type segment_command_64.
|
||||
pub fn getSections(lc: LoadCommand) []const section_64 {
|
||||
const segment_lc = lc.cast(segment_command_64).?;
|
||||
if (segment_lc.nsects == 0) return &[0]section_64{};
|
||||
const data = lc.data[@sizeOf(segment_command_64)..];
|
||||
const sections = @ptrCast(
|
||||
[*]const section_64,
|
||||
@alignCast(@alignOf(section_64), &data[0]),
|
||||
)[0..segment_lc.nsects];
|
||||
return sections;
|
||||
}
|
||||
|
||||
/// Asserts LoadCommand is of type dylib_command.
|
||||
pub fn getDylibPathName(lc: LoadCommand) []const u8 {
|
||||
const dylib_lc = lc.cast(dylib_command).?;
|
||||
const data = lc.data[dylib_lc.dylib.name..];
|
||||
return mem.sliceTo(data, 0);
|
||||
}
|
||||
|
||||
/// Asserts LoadCommand is of type rpath_command.
|
||||
pub fn getRpathPathName(lc: LoadCommand) []const u8 {
|
||||
const rpath_lc = lc.cast(rpath_command).?;
|
||||
const data = lc.data[rpath_lc.path..];
|
||||
return mem.sliceTo(data, 0);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn next(it: *LoadCommandIterator) ?LoadCommand {
|
||||
if (it.index >= it.ncmds) return null;
|
||||
|
||||
const hdr = @ptrCast(
|
||||
*const load_command,
|
||||
@alignCast(@alignOf(load_command), &it.buffer[0]),
|
||||
).*;
|
||||
const cmd = LoadCommand{
|
||||
.hdr = hdr,
|
||||
.data = it.buffer[0..hdr.cmdsize],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write(self: LoadCommand, writer: anytype) !void {
|
||||
return switch (self) {
|
||||
.dyld_info_only => |x| writeStruct(x, writer),
|
||||
.symtab => |x| writeStruct(x, writer),
|
||||
.dysymtab => |x| writeStruct(x, writer),
|
||||
.main => |x| writeStruct(x, writer),
|
||||
.version_min => |x| writeStruct(x, writer),
|
||||
.source_version => |x| writeStruct(x, writer),
|
||||
.uuid => |x| writeStruct(x, writer),
|
||||
.linkedit_data => |x| writeStruct(x, writer),
|
||||
.segment => |x| x.write(writer),
|
||||
.dylinker => |x| x.write(writer),
|
||||
.dylib => |x| x.write(writer),
|
||||
.rpath => |x| x.write(writer),
|
||||
.build_version => |x| x.write(writer),
|
||||
.unknown => |x| x.write(writer),
|
||||
};
|
||||
}
|
||||
it.buffer = @alignCast(@alignOf(u64), it.buffer[hdr.cmdsize..]);
|
||||
it.index += 1;
|
||||
|
||||
pub fn cmd(self: LoadCommand) LC {
|
||||
return switch (self) {
|
||||
.dyld_info_only => |x| x.cmd,
|
||||
.symtab => |x| x.cmd,
|
||||
.dysymtab => |x| x.cmd,
|
||||
.main => |x| x.cmd,
|
||||
.version_min => |x| x.cmd,
|
||||
.source_version => |x| x.cmd,
|
||||
.uuid => |x| x.cmd,
|
||||
.linkedit_data => |x| x.cmd,
|
||||
.segment => |x| x.inner.cmd,
|
||||
.dylinker => |x| x.inner.cmd,
|
||||
.dylib => |x| x.inner.cmd,
|
||||
.rpath => |x| x.inner.cmd,
|
||||
.build_version => |x| x.inner.cmd,
|
||||
.unknown => |x| x.inner.cmd,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn cmdsize(self: LoadCommand) u32 {
|
||||
return switch (self) {
|
||||
.dyld_info_only => |x| x.cmdsize,
|
||||
.symtab => |x| x.cmdsize,
|
||||
.dysymtab => |x| x.cmdsize,
|
||||
.main => |x| x.cmdsize,
|
||||
.version_min => |x| x.cmdsize,
|
||||
.source_version => |x| x.cmdsize,
|
||||
.linkedit_data => |x| x.cmdsize,
|
||||
.uuid => |x| x.cmdsize,
|
||||
.segment => |x| x.inner.cmdsize,
|
||||
.dylinker => |x| x.inner.cmdsize,
|
||||
.dylib => |x| x.inner.cmdsize,
|
||||
.rpath => |x| x.inner.cmdsize,
|
||||
.build_version => |x| x.inner.cmdsize,
|
||||
.unknown => |x| x.inner.cmdsize,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *LoadCommand, allocator: Allocator) void {
|
||||
return switch (self.*) {
|
||||
.segment => |*x| x.deinit(allocator),
|
||||
.dylinker => |*x| x.deinit(allocator),
|
||||
.dylib => |*x| x.deinit(allocator),
|
||||
.rpath => |*x| x.deinit(allocator),
|
||||
.build_version => |*x| x.deinit(allocator),
|
||||
.unknown => |*x| x.deinit(allocator),
|
||||
else => {},
|
||||
};
|
||||
}
|
||||
|
||||
fn writeStruct(command: anytype, writer: anytype) !void {
|
||||
return writer.writeAll(mem.asBytes(&command));
|
||||
}
|
||||
|
||||
pub fn eql(self: LoadCommand, other: LoadCommand) bool {
|
||||
if (@as(meta.Tag(LoadCommand), self) != @as(meta.Tag(LoadCommand), other)) return false;
|
||||
return switch (self) {
|
||||
.dyld_info_only => |x| meta.eql(x, other.dyld_info_only),
|
||||
.symtab => |x| meta.eql(x, other.symtab),
|
||||
.dysymtab => |x| meta.eql(x, other.dysymtab),
|
||||
.main => |x| meta.eql(x, other.main),
|
||||
.version_min => |x| meta.eql(x, other.version_min),
|
||||
.source_version => |x| meta.eql(x, other.source_version),
|
||||
.build_version => |x| x.eql(other.build_version),
|
||||
.uuid => |x| meta.eql(x, other.uuid),
|
||||
.linkedit_data => |x| meta.eql(x, other.linkedit_data),
|
||||
.segment => |x| x.eql(other.segment),
|
||||
.dylinker => |x| x.eql(other.dylinker),
|
||||
.dylib => |x| x.eql(other.dylib),
|
||||
.rpath => |x| x.eql(other.rpath),
|
||||
.unknown => |x| x.eql(other.unknown),
|
||||
};
|
||||
return cmd;
|
||||
}
|
||||
};
|
||||
|
||||
/// A Zig wrapper for segment_command_64.
|
||||
/// Encloses the extern struct together with a list of sections for this segment.
|
||||
pub const SegmentCommand = struct {
|
||||
inner: segment_command_64,
|
||||
sections: std.ArrayListUnmanaged(section_64) = .{},
|
||||
|
||||
pub fn read(allocator: Allocator, reader: anytype) !SegmentCommand {
|
||||
const inner = try reader.readStruct(segment_command_64);
|
||||
var segment = SegmentCommand{
|
||||
.inner = inner,
|
||||
};
|
||||
try segment.sections.ensureTotalCapacityPrecise(allocator, inner.nsects);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < inner.nsects) : (i += 1) {
|
||||
const sect = try reader.readStruct(section_64);
|
||||
segment.sections.appendAssumeCapacity(sect);
|
||||
}
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
pub fn write(self: SegmentCommand, writer: anytype) !void {
|
||||
try writer.writeAll(mem.asBytes(&self.inner));
|
||||
for (self.sections.items) |sect| {
|
||||
try writer.writeAll(mem.asBytes(§));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *SegmentCommand, allocator: Allocator) void {
|
||||
self.sections.deinit(allocator);
|
||||
}
|
||||
|
||||
pub fn eql(self: SegmentCommand, other: SegmentCommand) bool {
|
||||
if (!meta.eql(self.inner, other.inner)) return false;
|
||||
const lhs = self.sections.items;
|
||||
const rhs = other.sections.items;
|
||||
var i: usize = 0;
|
||||
while (i < self.inner.nsects) : (i += 1) {
|
||||
if (!meta.eql(lhs[i], rhs[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn emptyGenericCommandWithData(cmd: anytype) GenericCommandWithData(@TypeOf(cmd)) {
|
||||
return .{ .inner = cmd };
|
||||
}
|
||||
|
||||
/// A Zig wrapper for a generic load command with variable-length data.
|
||||
pub fn GenericCommandWithData(comptime Cmd: type) type {
|
||||
return struct {
|
||||
inner: Cmd,
|
||||
/// This field remains undefined until `read` is called.
|
||||
data: []u8 = undefined,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn read(allocator: Allocator, reader: anytype) !Self {
|
||||
const inner = try reader.readStruct(Cmd);
|
||||
var data = try allocator.alloc(u8, inner.cmdsize - @sizeOf(Cmd));
|
||||
errdefer allocator.free(data);
|
||||
try reader.readNoEof(data);
|
||||
return Self{
|
||||
.inner = inner,
|
||||
.data = data,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write(self: Self, writer: anytype) !void {
|
||||
try writer.writeAll(mem.asBytes(&self.inner));
|
||||
try writer.writeAll(self.data);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, allocator: Allocator) void {
|
||||
allocator.free(self.data);
|
||||
}
|
||||
|
||||
pub fn eql(self: Self, other: Self) bool {
|
||||
if (!meta.eql(self.inner, other.inner)) return false;
|
||||
return mem.eql(u8, self.data, other.data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createLoadDylibCommand(
|
||||
allocator: Allocator,
|
||||
cmd_id: LC,
|
||||
name: []const u8,
|
||||
timestamp: u32,
|
||||
current_version: u32,
|
||||
compatibility_version: u32,
|
||||
) !GenericCommandWithData(dylib_command) {
|
||||
assert(cmd_id == .LOAD_DYLIB or cmd_id == .LOAD_WEAK_DYLIB or cmd_id == .REEXPORT_DYLIB or cmd_id == .ID_DYLIB);
|
||||
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
|
||||
u64,
|
||||
@sizeOf(dylib_command) + name.len + 1, // +1 for nul
|
||||
@sizeOf(u64),
|
||||
));
|
||||
|
||||
var dylib_cmd = emptyGenericCommandWithData(dylib_command{
|
||||
.cmd = cmd_id,
|
||||
.cmdsize = cmdsize,
|
||||
.dylib = .{
|
||||
.name = @sizeOf(dylib_command),
|
||||
.timestamp = timestamp,
|
||||
.current_version = current_version,
|
||||
.compatibility_version = compatibility_version,
|
||||
},
|
||||
});
|
||||
dylib_cmd.data = try allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name);
|
||||
|
||||
mem.set(u8, dylib_cmd.data, 0);
|
||||
mem.copy(u8, dylib_cmd.data, name);
|
||||
|
||||
return dylib_cmd;
|
||||
}
|
||||
|
||||
fn testRead(allocator: Allocator, buffer: []const u8, expected: anytype) !void {
|
||||
var stream = io.fixedBufferStream(buffer);
|
||||
var given = try LoadCommand.read(allocator, stream.reader());
|
||||
defer given.deinit(allocator);
|
||||
try testing.expect(expected.eql(given));
|
||||
}
|
||||
|
||||
fn testWrite(buffer: []u8, cmd: LoadCommand, expected: []const u8) !void {
|
||||
var stream = io.fixedBufferStream(buffer);
|
||||
try cmd.write(stream.writer());
|
||||
try testing.expect(mem.eql(u8, expected, buffer[0..expected.len]));
|
||||
}
|
||||
|
||||
fn makeStaticString(bytes: []const u8) [16]u8 {
|
||||
var buf = [_]u8{0} ** 16;
|
||||
assert(bytes.len <= buf.len);
|
||||
mem.copy(u8, &buf, bytes);
|
||||
return buf;
|
||||
}
|
||||
|
||||
test "read-write segment command" {
|
||||
// TODO compiling for macOS from big-endian arch
|
||||
if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest;
|
||||
|
||||
var gpa = testing.allocator;
|
||||
const in_buffer = &[_]u8{
|
||||
0x19, 0x00, 0x00, 0x00, // cmd
|
||||
0x98, 0x00, 0x00, 0x00, // cmdsize
|
||||
0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segname
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // vmaddr
|
||||
0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // vmsize
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fileoff
|
||||
0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // filesize
|
||||
0x07, 0x00, 0x00, 0x00, // maxprot
|
||||
0x05, 0x00, 0x00, 0x00, // initprot
|
||||
0x01, 0x00, 0x00, 0x00, // nsects
|
||||
0x00, 0x00, 0x00, 0x00, // flags
|
||||
0x5f, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sectname
|
||||
0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segname
|
||||
0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // address
|
||||
0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size
|
||||
0x00, 0x40, 0x00, 0x00, // offset
|
||||
0x02, 0x00, 0x00, 0x00, // alignment
|
||||
0x00, 0x00, 0x00, 0x00, // reloff
|
||||
0x00, 0x00, 0x00, 0x00, // nreloc
|
||||
0x00, 0x04, 0x00, 0x80, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // reserved1
|
||||
0x00, 0x00, 0x00, 0x00, // reserved2
|
||||
0x00, 0x00, 0x00, 0x00, // reserved3
|
||||
};
|
||||
var cmd = SegmentCommand{
|
||||
.inner = .{
|
||||
.cmdsize = 152,
|
||||
.segname = makeStaticString("__TEXT"),
|
||||
.vmaddr = 4294967296,
|
||||
.vmsize = 294912,
|
||||
.filesize = 294912,
|
||||
.maxprot = PROT.READ | PROT.WRITE | PROT.EXEC,
|
||||
.initprot = PROT.EXEC | PROT.READ,
|
||||
.nsects = 1,
|
||||
},
|
||||
};
|
||||
try cmd.sections.append(gpa, .{
|
||||
.sectname = makeStaticString("__text"),
|
||||
.segname = makeStaticString("__TEXT"),
|
||||
.addr = 4294983680,
|
||||
.size = 448,
|
||||
.offset = 16384,
|
||||
.@"align" = 2,
|
||||
.flags = S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
|
||||
});
|
||||
defer cmd.deinit(gpa);
|
||||
try testRead(gpa, in_buffer, LoadCommand{ .segment = cmd });
|
||||
|
||||
var out_buffer: [in_buffer.len]u8 = undefined;
|
||||
try testWrite(&out_buffer, LoadCommand{ .segment = cmd }, in_buffer);
|
||||
}
|
||||
|
||||
test "read-write generic command with data" {
|
||||
// TODO compiling for macOS from big-endian arch
|
||||
if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest;
|
||||
|
||||
var gpa = testing.allocator;
|
||||
const in_buffer = &[_]u8{
|
||||
0x0c, 0x00, 0x00, 0x00, // cmd
|
||||
0x20, 0x00, 0x00, 0x00, // cmdsize
|
||||
0x18, 0x00, 0x00, 0x00, // name
|
||||
0x02, 0x00, 0x00, 0x00, // timestamp
|
||||
0x00, 0x00, 0x00, 0x00, // current_version
|
||||
0x00, 0x00, 0x00, 0x00, // compatibility_version
|
||||
0x2f, 0x75, 0x73, 0x72, 0x00, 0x00, 0x00, 0x00, // data
|
||||
};
|
||||
var cmd = GenericCommandWithData(dylib_command){
|
||||
.inner = .{
|
||||
.cmd = .LOAD_DYLIB,
|
||||
.cmdsize = 32,
|
||||
.dylib = .{
|
||||
.name = 24,
|
||||
.timestamp = 2,
|
||||
.current_version = 0,
|
||||
.compatibility_version = 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
cmd.data = try gpa.alloc(u8, 8);
|
||||
defer gpa.free(cmd.data);
|
||||
cmd.data[0] = 0x2f;
|
||||
cmd.data[1] = 0x75;
|
||||
cmd.data[2] = 0x73;
|
||||
cmd.data[3] = 0x72;
|
||||
cmd.data[4] = 0x0;
|
||||
cmd.data[5] = 0x0;
|
||||
cmd.data[6] = 0x0;
|
||||
cmd.data[7] = 0x0;
|
||||
try testRead(gpa, in_buffer, LoadCommand{ .dylib = cmd });
|
||||
|
||||
var out_buffer: [in_buffer.len]u8 = undefined;
|
||||
try testWrite(&out_buffer, LoadCommand{ .dylib = cmd }, in_buffer);
|
||||
}
|
||||
|
||||
test "read-write C struct command" {
|
||||
// TODO compiling for macOS from big-endian arch
|
||||
if (builtin.target.cpu.arch.endian() != .Little) return error.SkipZigTest;
|
||||
|
||||
var gpa = testing.allocator;
|
||||
const in_buffer = &[_]u8{
|
||||
0x28, 0x00, 0x00, 0x80, // cmd
|
||||
0x18, 0x00, 0x00, 0x00, // cmdsize
|
||||
0x04, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // entryoff
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stacksize
|
||||
};
|
||||
const cmd = .{
|
||||
.cmd = .MAIN,
|
||||
.cmdsize = 24,
|
||||
.entryoff = 16644,
|
||||
.stacksize = 0,
|
||||
};
|
||||
try testRead(gpa, in_buffer, LoadCommand{ .main = cmd });
|
||||
|
||||
var out_buffer: [in_buffer.len]u8 = undefined;
|
||||
try testWrite(&out_buffer, LoadCommand{ .main = cmd }, in_buffer);
|
||||
}
|
||||
|
||||
137
src/AstGen.zig
137
src/AstGen.zig
@ -768,12 +768,12 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
|
||||
.if_simple => return ifExpr(gz, scope, rl.br(), node, tree.ifSimple(node)),
|
||||
.@"if" => return ifExpr(gz, scope, rl.br(), node, tree.ifFull(node)),
|
||||
|
||||
.while_simple => return whileExpr(gz, scope, rl.br(), node, tree.whileSimple(node)),
|
||||
.while_cont => return whileExpr(gz, scope, rl.br(), node, tree.whileCont(node)),
|
||||
.@"while" => return whileExpr(gz, scope, rl.br(), node, tree.whileFull(node)),
|
||||
.while_simple => return whileExpr(gz, scope, rl.br(), node, tree.whileSimple(node), false),
|
||||
.while_cont => return whileExpr(gz, scope, rl.br(), node, tree.whileCont(node), false),
|
||||
.@"while" => return whileExpr(gz, scope, rl.br(), node, tree.whileFull(node), false),
|
||||
|
||||
.for_simple => return forExpr(gz, scope, rl.br(), node, tree.forSimple(node)),
|
||||
.@"for" => return forExpr(gz, scope, rl.br(), node, tree.forFull(node)),
|
||||
.for_simple => return forExpr(gz, scope, rl.br(), node, tree.forSimple(node), false),
|
||||
.@"for" => return forExpr(gz, scope, rl.br(), node, tree.forFull(node), false),
|
||||
|
||||
.slice_open => {
|
||||
const lhs = try expr(gz, scope, .ref, node_datas[node].lhs);
|
||||
@ -1899,6 +1899,17 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
|
||||
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
|
||||
.namespace => break,
|
||||
.defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
|
||||
.defer_gen => {
|
||||
const defer_gen = scope.cast(Scope.DeferGen).?;
|
||||
|
||||
return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
|
||||
try astgen.errNoteNode(
|
||||
defer_gen.defer_node,
|
||||
"defer expression here",
|
||||
.{},
|
||||
),
|
||||
});
|
||||
},
|
||||
.top => unreachable,
|
||||
}
|
||||
}
|
||||
@ -1958,6 +1969,17 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
|
||||
try unusedResultDeferExpr(parent_gz, defer_scope, defer_scope.parent, expr_node);
|
||||
},
|
||||
.defer_error => scope = scope.cast(Scope.Defer).?.parent,
|
||||
.defer_gen => {
|
||||
const defer_gen = scope.cast(Scope.DeferGen).?;
|
||||
|
||||
return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
|
||||
try astgen.errNoteNode(
|
||||
defer_gen.defer_node,
|
||||
"defer expression here",
|
||||
.{},
|
||||
),
|
||||
});
|
||||
},
|
||||
.namespace => break,
|
||||
.top => unreachable,
|
||||
}
|
||||
@ -2022,6 +2044,7 @@ fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: Ast.Toke
|
||||
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
|
||||
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
|
||||
.defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
|
||||
.defer_gen => scope = scope.cast(Scope.DeferGen).?.parent,
|
||||
.namespace => break,
|
||||
.top => unreachable,
|
||||
}
|
||||
@ -2129,6 +2152,7 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
|
||||
const astgen = gz.astgen;
|
||||
const tree = astgen.tree;
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const node_data = tree.nodes.items(.data);
|
||||
|
||||
if (statements.len == 0) return;
|
||||
|
||||
@ -2155,8 +2179,10 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
|
||||
},
|
||||
);
|
||||
}
|
||||
switch (node_tags[statement]) {
|
||||
// zig fmt: off
|
||||
var inner_node = statement;
|
||||
while (true) {
|
||||
switch (node_tags[inner_node]) {
|
||||
// zig fmt: off
|
||||
.global_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.globalVarDecl(statement)),
|
||||
.local_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.localVarDecl(statement)),
|
||||
.simple_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.simpleVarDecl(statement)),
|
||||
@ -2181,9 +2207,23 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
|
||||
.assign_add_wrap => try assignOp(gz, scope, statement, .addwrap),
|
||||
.assign_mul => try assignOp(gz, scope, statement, .mul),
|
||||
.assign_mul_wrap => try assignOp(gz, scope, statement, .mulwrap),
|
||||
|
||||
.grouped_expression => {
|
||||
inner_node = node_data[statement].lhs;
|
||||
continue;
|
||||
},
|
||||
|
||||
else => noreturn_src_node = try unusedResultExpr(gz, scope, statement),
|
||||
.while_simple => _ = try whileExpr(gz, scope, .discard, inner_node, tree.whileSimple(inner_node), true),
|
||||
.while_cont => _ = try whileExpr(gz, scope, .discard, inner_node, tree.whileCont(inner_node), true),
|
||||
.@"while" => _ = try whileExpr(gz, scope, .discard, inner_node, tree.whileFull(inner_node), true),
|
||||
|
||||
.for_simple => _ = try forExpr(gz, scope, .discard, inner_node, tree.forSimple(inner_node), true),
|
||||
.@"for" => _ = try forExpr(gz, scope, .discard, inner_node, tree.forFull(inner_node), true),
|
||||
|
||||
else => noreturn_src_node = try unusedResultExpr(gz, scope, inner_node),
|
||||
// zig fmt: on
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2206,7 +2246,13 @@ fn unusedResultDeferExpr(gz: *GenZir, defer_scope: *Scope.Defer, expr_scope: *Sc
|
||||
astgen.source_offset = defer_scope.source_offset;
|
||||
astgen.source_line = defer_scope.source_line;
|
||||
astgen.source_column = defer_scope.source_column;
|
||||
_ = try unusedResultExpr(gz, expr_scope, expr_node);
|
||||
|
||||
var defer_gen: Scope.DeferGen = .{
|
||||
.parent = expr_scope,
|
||||
.defer_node = defer_scope.defer_node,
|
||||
};
|
||||
|
||||
_ = try unusedResultExpr(gz, &defer_gen.base, expr_node);
|
||||
}
|
||||
|
||||
/// Returns AST source node of the thing that is noreturn if the statement is
|
||||
@ -2216,6 +2262,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
|
||||
// We need to emit an error if the result is not `noreturn` or `void`, but
|
||||
// we want to avoid adding the ZIR instruction if possible for performance.
|
||||
const maybe_unused_result = try expr(gz, scope, .none, statement);
|
||||
return addEnsureResult(gz, maybe_unused_result, statement);
|
||||
}
|
||||
|
||||
fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: Ast.Node.Index) InnerError!Ast.Node.Index {
|
||||
var noreturn_src_node: Ast.Node.Index = 0;
|
||||
const elide_check = if (refToIndex(maybe_unused_result)) |inst| b: {
|
||||
// Note that this array becomes invalid after appending more items to it
|
||||
@ -2553,6 +2603,7 @@ fn countDefers(astgen: *AstGen, outer_scope: *Scope, inner_scope: *Scope) struct
|
||||
.gen_zir => scope = scope.cast(GenZir).?.parent,
|
||||
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
|
||||
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
|
||||
.defer_gen => scope = scope.cast(Scope.DeferGen).?.parent,
|
||||
.defer_normal => {
|
||||
const defer_scope = scope.cast(Scope.Defer).?;
|
||||
scope = defer_scope.parent;
|
||||
@ -2602,6 +2653,7 @@ fn genDefers(
|
||||
.gen_zir => scope = scope.cast(GenZir).?.parent,
|
||||
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
|
||||
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
|
||||
.defer_gen => scope = scope.cast(Scope.DeferGen).?.parent,
|
||||
.defer_normal => {
|
||||
const defer_scope = scope.cast(Scope.Defer).?;
|
||||
scope = defer_scope.parent;
|
||||
@ -2644,6 +2696,7 @@ fn genDefers(
|
||||
break :blk &local_val_scope.base;
|
||||
};
|
||||
try unusedResultDeferExpr(gz, defer_scope, sub_scope, expr_node);
|
||||
try checkUsed(gz, scope, sub_scope);
|
||||
try gz.addDbgBlockEnd();
|
||||
},
|
||||
.normal_only => continue,
|
||||
@ -2681,6 +2734,7 @@ fn checkUsed(
|
||||
scope = s.parent;
|
||||
},
|
||||
.defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
|
||||
.defer_gen => scope = scope.cast(Scope.DeferGen).?.parent,
|
||||
.namespace => unreachable,
|
||||
.top => unreachable,
|
||||
}
|
||||
@ -4040,6 +4094,7 @@ fn testDecl(
|
||||
.local_val, .local_ptr => unreachable, // a test cannot be in a local scope
|
||||
.gen_zir => s = s.cast(GenZir).?.parent,
|
||||
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
|
||||
.defer_gen => s = s.cast(Scope.DeferGen).?.parent,
|
||||
.namespace => {
|
||||
const ns = s.cast(Scope.Namespace).?;
|
||||
if (ns.decls.get(name_str_index)) |i| {
|
||||
@ -5330,7 +5385,7 @@ fn ifExpr(
|
||||
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_err_ptr else .is_non_err;
|
||||
break :c .{
|
||||
.inst = err_union,
|
||||
.bool_bit = try block_scope.addUnNode(tag, err_union, node),
|
||||
.bool_bit = try block_scope.addUnNode(tag, err_union, if_full.ast.cond_expr),
|
||||
};
|
||||
} else if (if_full.payload_token) |_| {
|
||||
const cond_rl: ResultLoc = if (payload_is_ref) .ref else .none;
|
||||
@ -5338,7 +5393,7 @@ fn ifExpr(
|
||||
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_null_ptr else .is_non_null;
|
||||
break :c .{
|
||||
.inst = optional,
|
||||
.bool_bit = try block_scope.addUnNode(tag, optional, node),
|
||||
.bool_bit = try block_scope.addUnNode(tag, optional, if_full.ast.cond_expr),
|
||||
};
|
||||
} else {
|
||||
const cond = try expr(&block_scope, &block_scope.base, bool_rl, if_full.ast.cond_expr);
|
||||
@ -5369,7 +5424,7 @@ fn ifExpr(
|
||||
.err_union_payload_unsafe_ptr
|
||||
else
|
||||
.err_union_payload_unsafe;
|
||||
const payload_inst = try then_scope.addUnNode(tag, cond.inst, node);
|
||||
const payload_inst = try then_scope.addUnNode(tag, cond.inst, if_full.ast.then_expr);
|
||||
const token_name_index = payload_token + @boolToInt(payload_is_ref);
|
||||
const ident_name = try astgen.identAsString(token_name_index);
|
||||
const token_name_str = tree.tokenSlice(token_name_index);
|
||||
@ -5398,7 +5453,7 @@ fn ifExpr(
|
||||
const ident_bytes = tree.tokenSlice(ident_token);
|
||||
if (mem.eql(u8, "_", ident_bytes))
|
||||
break :s &then_scope.base;
|
||||
const payload_inst = try then_scope.addUnNode(tag, cond.inst, node);
|
||||
const payload_inst = try then_scope.addUnNode(tag, cond.inst, if_full.ast.then_expr);
|
||||
const ident_name = try astgen.identAsString(ident_token);
|
||||
try astgen.detectLocalShadowing(&then_scope.base, ident_name, ident_token, ident_bytes);
|
||||
payload_val_scope = .{
|
||||
@ -5441,7 +5496,7 @@ fn ifExpr(
|
||||
.err_union_code_ptr
|
||||
else
|
||||
.err_union_code;
|
||||
const payload_inst = try else_scope.addUnNode(tag, cond.inst, node);
|
||||
const payload_inst = try else_scope.addUnNode(tag, cond.inst, if_full.ast.cond_expr);
|
||||
const ident_name = try astgen.identAsString(error_token);
|
||||
const error_token_str = tree.tokenSlice(error_token);
|
||||
if (mem.eql(u8, "_", error_token_str))
|
||||
@ -5615,6 +5670,7 @@ fn whileExpr(
|
||||
rl: ResultLoc,
|
||||
node: Ast.Node.Index,
|
||||
while_full: Ast.full.While,
|
||||
is_statement: bool,
|
||||
) InnerError!Zir.Inst.Ref {
|
||||
const astgen = parent_gz.astgen;
|
||||
const tree = astgen.tree;
|
||||
@ -5654,7 +5710,7 @@ fn whileExpr(
|
||||
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_err_ptr else .is_non_err;
|
||||
break :c .{
|
||||
.inst = err_union,
|
||||
.bool_bit = try continue_scope.addUnNode(tag, err_union, node),
|
||||
.bool_bit = try continue_scope.addUnNode(tag, err_union, while_full.ast.then_expr),
|
||||
};
|
||||
} else if (while_full.payload_token) |_| {
|
||||
const cond_rl: ResultLoc = if (payload_is_ref) .ref else .none;
|
||||
@ -5662,7 +5718,7 @@ fn whileExpr(
|
||||
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_null_ptr else .is_non_null;
|
||||
break :c .{
|
||||
.inst = optional,
|
||||
.bool_bit = try continue_scope.addUnNode(tag, optional, node),
|
||||
.bool_bit = try continue_scope.addUnNode(tag, optional, while_full.ast.then_expr),
|
||||
};
|
||||
} else {
|
||||
const cond = try expr(&continue_scope, &continue_scope.base, bool_rl, while_full.ast.cond_expr);
|
||||
@ -5700,7 +5756,7 @@ fn whileExpr(
|
||||
else
|
||||
.err_union_payload_unsafe;
|
||||
// will add this instruction to then_scope.instructions below
|
||||
payload_inst = try then_scope.makeUnNode(tag, cond.inst, node);
|
||||
payload_inst = try then_scope.makeUnNode(tag, cond.inst, while_full.ast.cond_expr);
|
||||
const ident_token = if (payload_is_ref) payload_token + 1 else payload_token;
|
||||
const ident_bytes = tree.tokenSlice(ident_token);
|
||||
if (mem.eql(u8, "_", ident_bytes))
|
||||
@ -5729,7 +5785,7 @@ fn whileExpr(
|
||||
else
|
||||
.optional_payload_unsafe;
|
||||
// will add this instruction to then_scope.instructions below
|
||||
payload_inst = try then_scope.makeUnNode(tag, cond.inst, node);
|
||||
payload_inst = try then_scope.makeUnNode(tag, cond.inst, while_full.ast.cond_expr);
|
||||
const ident_name = try astgen.identAsString(ident_token);
|
||||
const ident_bytes = tree.tokenSlice(ident_token);
|
||||
if (mem.eql(u8, "_", ident_bytes))
|
||||
@ -5785,6 +5841,8 @@ fn whileExpr(
|
||||
try then_scope.addDbgVar(.dbg_var_val, some, dbg_var_inst);
|
||||
}
|
||||
const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);
|
||||
_ = try addEnsureResult(&then_scope, then_result, while_full.ast.then_expr);
|
||||
|
||||
try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
|
||||
try then_scope.addDbgBlockEnd();
|
||||
|
||||
@ -5803,7 +5861,7 @@ fn whileExpr(
|
||||
.err_union_code_ptr
|
||||
else
|
||||
.err_union_code;
|
||||
const else_payload_inst = try else_scope.addUnNode(tag, cond.inst, node);
|
||||
const else_payload_inst = try else_scope.addUnNode(tag, cond.inst, while_full.ast.cond_expr);
|
||||
const ident_name = try astgen.identAsString(error_token);
|
||||
const ident_bytes = tree.tokenSlice(error_token);
|
||||
if (mem.eql(u8, ident_bytes, "_"))
|
||||
@ -5827,7 +5885,11 @@ fn whileExpr(
|
||||
// control flow apply to outer loops; not this one.
|
||||
loop_scope.continue_block = 0;
|
||||
loop_scope.break_block = 0;
|
||||
const e = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);
|
||||
const else_result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);
|
||||
if (is_statement) {
|
||||
_ = try addEnsureResult(&else_scope, else_result, else_node);
|
||||
}
|
||||
|
||||
if (!else_scope.endsWithNoReturn()) {
|
||||
loop_scope.break_count += 1;
|
||||
}
|
||||
@ -5835,7 +5897,7 @@ fn whileExpr(
|
||||
try else_scope.addDbgBlockEnd();
|
||||
break :blk .{
|
||||
.src = else_node,
|
||||
.result = e,
|
||||
.result = else_result,
|
||||
};
|
||||
} else .{
|
||||
.src = while_full.ast.then_expr,
|
||||
@ -5848,7 +5910,7 @@ fn whileExpr(
|
||||
}
|
||||
}
|
||||
const break_tag: Zir.Inst.Tag = if (is_inline) .break_inline else .@"break";
|
||||
return finishThenElseBlock(
|
||||
const result = try finishThenElseBlock(
|
||||
parent_gz,
|
||||
rl,
|
||||
node,
|
||||
@ -5863,6 +5925,10 @@ fn whileExpr(
|
||||
cond_block,
|
||||
break_tag,
|
||||
);
|
||||
if (is_statement) {
|
||||
_ = try parent_gz.addUnNode(.ensure_result_used, result, node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn forExpr(
|
||||
@ -5871,6 +5937,7 @@ fn forExpr(
|
||||
rl: ResultLoc,
|
||||
node: Ast.Node.Index,
|
||||
for_full: Ast.full.While,
|
||||
is_statement: bool,
|
||||
) InnerError!Zir.Inst.Ref {
|
||||
const astgen = parent_gz.astgen;
|
||||
|
||||
@ -6014,6 +6081,8 @@ fn forExpr(
|
||||
};
|
||||
|
||||
const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);
|
||||
_ = try addEnsureResult(&then_scope, then_result, for_full.ast.then_expr);
|
||||
|
||||
try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
|
||||
try then_scope.addDbgBlockEnd();
|
||||
|
||||
@ -6031,6 +6100,10 @@ fn forExpr(
|
||||
loop_scope.continue_block = 0;
|
||||
loop_scope.break_block = 0;
|
||||
const else_result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);
|
||||
if (is_statement) {
|
||||
_ = try addEnsureResult(&else_scope, else_result, else_node);
|
||||
}
|
||||
|
||||
if (!else_scope.endsWithNoReturn()) {
|
||||
loop_scope.break_count += 1;
|
||||
}
|
||||
@ -6049,7 +6122,7 @@ fn forExpr(
|
||||
}
|
||||
}
|
||||
const break_tag: Zir.Inst.Tag = if (is_inline) .break_inline else .@"break";
|
||||
return finishThenElseBlock(
|
||||
const result = try finishThenElseBlock(
|
||||
parent_gz,
|
||||
rl,
|
||||
node,
|
||||
@ -6064,6 +6137,10 @@ fn forExpr(
|
||||
cond_block,
|
||||
break_tag,
|
||||
);
|
||||
if (is_statement) {
|
||||
_ = try parent_gz.addUnNode(.ensure_result_used, result, node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn switchExpr(
|
||||
@ -6730,6 +6807,7 @@ fn localVarRef(
|
||||
},
|
||||
.gen_zir => s = s.cast(GenZir).?.parent,
|
||||
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
|
||||
.defer_gen => s = s.cast(Scope.DeferGen).?.parent,
|
||||
.namespace => {
|
||||
const ns = s.cast(Scope.Namespace).?;
|
||||
if (ns.decls.get(name_str_index)) |i| {
|
||||
@ -7351,6 +7429,7 @@ fn builtinCall(
|
||||
},
|
||||
.gen_zir => s = s.cast(GenZir).?.parent,
|
||||
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
|
||||
.defer_gen => s = s.cast(Scope.DeferGen).?.parent,
|
||||
.namespace => {
|
||||
const ns = s.cast(Scope.Namespace).?;
|
||||
if (ns.decls.get(decl_name)) |i| {
|
||||
@ -7424,7 +7503,8 @@ fn builtinCall(
|
||||
const token_starts = tree.tokens.items(.start);
|
||||
const node_start = token_starts[tree.firstToken(node)];
|
||||
astgen.advanceSourceCursor(node_start);
|
||||
const result = try gz.addExtendedPayload(.builtin_src, Zir.Inst.LineColumn{
|
||||
const result = try gz.addExtendedPayload(.builtin_src, Zir.Inst.Src{
|
||||
.node = gz.nodeIndexToRelative(node),
|
||||
.line = astgen.source_line,
|
||||
.column = astgen.source_column,
|
||||
});
|
||||
@ -9807,6 +9887,7 @@ const Scope = struct {
|
||||
local_ptr,
|
||||
defer_normal,
|
||||
defer_error,
|
||||
defer_gen,
|
||||
namespace,
|
||||
top,
|
||||
};
|
||||
@ -9904,6 +9985,13 @@ const Scope = struct {
|
||||
const base_tag: Scope.Tag = .top;
|
||||
base: Scope = Scope{ .tag = base_tag },
|
||||
};
|
||||
|
||||
const DeferGen = struct {
|
||||
const base_tag: Scope.Tag = .defer_gen;
|
||||
base: Scope = Scope{ .tag = base_tag },
|
||||
parent: *Scope,
|
||||
defer_node: Ast.Node.Index,
|
||||
};
|
||||
};
|
||||
|
||||
/// This is a temporary structure; references to it are valid only
|
||||
@ -11414,6 +11502,7 @@ fn detectLocalShadowing(
|
||||
},
|
||||
.gen_zir => s = s.cast(GenZir).?.parent,
|
||||
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
|
||||
.defer_gen => s = s.cast(Scope.DeferGen).?.parent,
|
||||
.top => break,
|
||||
};
|
||||
}
|
||||
|
||||
@ -468,7 +468,7 @@ const DocData = struct {
|
||||
child: Expr,
|
||||
},
|
||||
ErrorUnion: struct { lhs: Expr, rhs: Expr },
|
||||
// ErrorUnion: struct { name: []const u8 },
|
||||
InferredErrorUnion: struct { payload: Expr },
|
||||
ErrorSet: struct {
|
||||
name: []const u8,
|
||||
fields: ?[]const Field = null,
|
||||
@ -580,8 +580,9 @@ const DocData = struct {
|
||||
enumLiteral: []const u8, // direct value
|
||||
alignOf: usize, // index in `exprs`
|
||||
typeOf: usize, // index in `exprs`
|
||||
typeInfo: usize, // index in `exprs`
|
||||
typeOf_peer: []usize,
|
||||
errorUnion: usize, // index in `exprs`
|
||||
errorUnion: usize, // index in `types`
|
||||
as: As,
|
||||
sizeOf: usize, // index in `exprs`
|
||||
bitSizeOf: usize, // index in `exprs`
|
||||
@ -1398,7 +1399,6 @@ fn walkInstruction(
|
||||
const extra = file.zir.extraData(Zir.Inst.PtrType, ptr.payload_index);
|
||||
var extra_index = extra.end;
|
||||
|
||||
const type_slot_index = self.types.items.len;
|
||||
const elem_type_ref = try self.walkRef(
|
||||
file,
|
||||
parent_scope,
|
||||
@ -1445,6 +1445,7 @@ fn walkInstruction(
|
||||
host_size = ref_result.expr;
|
||||
}
|
||||
|
||||
const type_slot_index = self.types.items.len;
|
||||
try self.types.append(self.arena, .{
|
||||
.Pointer = .{
|
||||
.size = ptr.size,
|
||||
@ -1788,7 +1789,7 @@ fn walkInstruction(
|
||||
|
||||
return DocData.WalkResult{
|
||||
.typeRef = operand.typeRef,
|
||||
.expr = .{ .typeOf = operand_index },
|
||||
.expr = .{ .typeInfo = operand_index },
|
||||
};
|
||||
},
|
||||
.as_node => {
|
||||
@ -1928,7 +1929,7 @@ fn walkInstruction(
|
||||
.comptimeExpr = self.comptime_exprs.items.len,
|
||||
} };
|
||||
try self.comptime_exprs.append(self.arena, .{
|
||||
.code = "if(banana) 1 else 0",
|
||||
.code = "if (...) { ... }",
|
||||
});
|
||||
return res;
|
||||
},
|
||||
@ -2118,6 +2119,7 @@ fn walkInstruction(
|
||||
inst_index,
|
||||
self_ast_node_index,
|
||||
type_slot_index,
|
||||
tags[inst_index] == .func_inferred,
|
||||
);
|
||||
|
||||
return result;
|
||||
@ -2993,7 +2995,7 @@ fn tryResolveRefPath(
|
||||
"TODO: handle `{s}`in tryResolveRefPath\nInfo: {}",
|
||||
.{ @tagName(resolved_parent), resolved_parent },
|
||||
);
|
||||
path[i + 1] = (try self.cteTodo("match failure")).expr;
|
||||
path[i + 1] = (try self.cteTodo("<match failure>")).expr;
|
||||
continue :outer;
|
||||
},
|
||||
.comptimeExpr, .call, .typeOf => {
|
||||
@ -3415,6 +3417,7 @@ fn analyzeFunction(
|
||||
inst_index: usize,
|
||||
self_ast_node_index: usize,
|
||||
type_slot_index: usize,
|
||||
ret_is_inferred_error_set: bool,
|
||||
) AutodocErrors!DocData.WalkResult {
|
||||
const tags = file.zir.instructions.items(.tag);
|
||||
const data = file.zir.instructions.items(.data);
|
||||
@ -3521,13 +3524,23 @@ fn analyzeFunction(
|
||||
else => null,
|
||||
};
|
||||
|
||||
const ret_type: DocData.Expr = blk: {
|
||||
if (ret_is_inferred_error_set) {
|
||||
const ret_type_slot_index = self.types.items.len;
|
||||
try self.types.append(self.arena, .{
|
||||
.InferredErrorUnion = .{ .payload = ret_type_ref },
|
||||
});
|
||||
break :blk .{ .type = ret_type_slot_index };
|
||||
} else break :blk ret_type_ref;
|
||||
};
|
||||
|
||||
self.ast_nodes.items[self_ast_node_index].fields = param_ast_indexes.items;
|
||||
self.types.items[type_slot_index] = .{
|
||||
.Fn = .{
|
||||
.name = "todo_name func",
|
||||
.src = self_ast_node_index,
|
||||
.params = param_type_refs.items,
|
||||
.ret = ret_type_ref,
|
||||
.ret = ret_type,
|
||||
.generic_ret = generic_ret,
|
||||
},
|
||||
};
|
||||
|
||||
107
src/Module.zig
107
src/Module.zig
@ -2161,6 +2161,10 @@ pub const SrcLoc = struct {
|
||||
.local_var_decl => tree.localVarDecl(node),
|
||||
.simple_var_decl => tree.simpleVarDecl(node),
|
||||
.aligned_var_decl => tree.alignedVarDecl(node),
|
||||
.@"usingnamespace" => {
|
||||
const node_data = tree.nodes.items(.data);
|
||||
return nodeToSpan(tree, node_data[node].lhs);
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
if (full.ast.type_node != 0) {
|
||||
@ -2171,6 +2175,58 @@ pub const SrcLoc = struct {
|
||||
const end = start + @intCast(u32, tree.tokenSlice(tok_index).len);
|
||||
return Span{ .start = start, .end = end, .main = start };
|
||||
},
|
||||
.node_offset_var_decl_align => |node_off| {
|
||||
const tree = try src_loc.file_scope.getTree(gpa);
|
||||
const node = src_loc.declRelativeToNodeIndex(node_off);
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const full: Ast.full.VarDecl = switch (node_tags[node]) {
|
||||
.global_var_decl => tree.globalVarDecl(node),
|
||||
.local_var_decl => tree.localVarDecl(node),
|
||||
.simple_var_decl => tree.simpleVarDecl(node),
|
||||
.aligned_var_decl => tree.alignedVarDecl(node),
|
||||
else => unreachable,
|
||||
};
|
||||
return nodeToSpan(tree, full.ast.align_node);
|
||||
},
|
||||
.node_offset_var_decl_section => |node_off| {
|
||||
const tree = try src_loc.file_scope.getTree(gpa);
|
||||
const node = src_loc.declRelativeToNodeIndex(node_off);
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const full: Ast.full.VarDecl = switch (node_tags[node]) {
|
||||
.global_var_decl => tree.globalVarDecl(node),
|
||||
.local_var_decl => tree.localVarDecl(node),
|
||||
.simple_var_decl => tree.simpleVarDecl(node),
|
||||
.aligned_var_decl => tree.alignedVarDecl(node),
|
||||
else => unreachable,
|
||||
};
|
||||
return nodeToSpan(tree, full.ast.section_node);
|
||||
},
|
||||
.node_offset_var_decl_addrspace => |node_off| {
|
||||
const tree = try src_loc.file_scope.getTree(gpa);
|
||||
const node = src_loc.declRelativeToNodeIndex(node_off);
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const full: Ast.full.VarDecl = switch (node_tags[node]) {
|
||||
.global_var_decl => tree.globalVarDecl(node),
|
||||
.local_var_decl => tree.localVarDecl(node),
|
||||
.simple_var_decl => tree.simpleVarDecl(node),
|
||||
.aligned_var_decl => tree.alignedVarDecl(node),
|
||||
else => unreachable,
|
||||
};
|
||||
return nodeToSpan(tree, full.ast.addrspace_node);
|
||||
},
|
||||
.node_offset_var_decl_init => |node_off| {
|
||||
const tree = try src_loc.file_scope.getTree(gpa);
|
||||
const node = src_loc.declRelativeToNodeIndex(node_off);
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const full: Ast.full.VarDecl = switch (node_tags[node]) {
|
||||
.global_var_decl => tree.globalVarDecl(node),
|
||||
.local_var_decl => tree.localVarDecl(node),
|
||||
.simple_var_decl => tree.simpleVarDecl(node),
|
||||
.aligned_var_decl => tree.alignedVarDecl(node),
|
||||
else => unreachable,
|
||||
};
|
||||
return nodeToSpan(tree, full.ast.init_node);
|
||||
},
|
||||
.node_offset_builtin_call_arg0 => |n| return src_loc.byteOffsetBuiltinCallArg(gpa, n, 0),
|
||||
.node_offset_builtin_call_arg1 => |n| return src_loc.byteOffsetBuiltinCallArg(gpa, n, 1),
|
||||
.node_offset_builtin_call_arg2 => |n| return src_loc.byteOffsetBuiltinCallArg(gpa, n, 2),
|
||||
@ -2857,6 +2913,18 @@ pub const LazySrcLoc = union(enum) {
|
||||
/// to the type expression.
|
||||
/// The Decl is determined contextually.
|
||||
node_offset_var_decl_ty: i32,
|
||||
/// The source location points to the alignment expression of a var decl.
|
||||
/// The Decl is determined contextually.
|
||||
node_offset_var_decl_align: i32,
|
||||
/// The source location points to the linksection expression of a var decl.
|
||||
/// The Decl is determined contextually.
|
||||
node_offset_var_decl_section: i32,
|
||||
/// The source location points to the addrspace expression of a var decl.
|
||||
/// The Decl is determined contextually.
|
||||
node_offset_var_decl_addrspace: i32,
|
||||
/// The source location points to the initializer of a var decl.
|
||||
/// The Decl is determined contextually.
|
||||
node_offset_var_decl_init: i32,
|
||||
/// The source location points to a for loop condition expression,
|
||||
/// found by taking this AST node index offset from the containing
|
||||
/// Decl AST node, which points to a for loop AST node. Next, navigate
|
||||
@ -3098,6 +3166,10 @@ pub const LazySrcLoc = union(enum) {
|
||||
.node_offset,
|
||||
.node_offset_initializer,
|
||||
.node_offset_var_decl_ty,
|
||||
.node_offset_var_decl_align,
|
||||
.node_offset_var_decl_section,
|
||||
.node_offset_var_decl_addrspace,
|
||||
.node_offset_var_decl_init,
|
||||
.node_offset_for_cond,
|
||||
.node_offset_builtin_call_arg0,
|
||||
.node_offset_builtin_call_arg1,
|
||||
@ -4414,17 +4486,26 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
|
||||
const body = zir.extra[extra.end..][0..extra.data.body_len];
|
||||
const result_ref = (try sema.analyzeBodyBreak(&block_scope, body)).?.operand;
|
||||
try wip_captures.finalize();
|
||||
const src = LazySrcLoc.nodeOffset(0);
|
||||
const decl_tv = try sema.resolveInstValue(&block_scope, .unneeded, result_ref, undefined);
|
||||
const align_src: LazySrcLoc = .{ .node_offset_var_decl_align = 0 };
|
||||
const section_src: LazySrcLoc = .{ .node_offset_var_decl_section = 0 };
|
||||
const address_space_src: LazySrcLoc = .{ .node_offset_var_decl_addrspace = 0 };
|
||||
const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = 0 };
|
||||
const init_src: LazySrcLoc = .{ .node_offset_var_decl_init = 0 };
|
||||
const decl_tv = try sema.resolveInstValue(&block_scope, init_src, result_ref, undefined);
|
||||
const decl_align: u32 = blk: {
|
||||
const align_ref = decl.zirAlignRef();
|
||||
if (align_ref == .none) break :blk 0;
|
||||
break :blk try sema.resolveAlign(&block_scope, src, align_ref);
|
||||
break :blk try sema.resolveAlign(&block_scope, align_src, align_ref);
|
||||
};
|
||||
const decl_linksection: ?[*:0]const u8 = blk: {
|
||||
const linksection_ref = decl.zirLinksectionRef();
|
||||
if (linksection_ref == .none) break :blk null;
|
||||
const bytes = try sema.resolveConstString(&block_scope, src, linksection_ref, "linksection must be comptime known");
|
||||
const bytes = try sema.resolveConstString(&block_scope, section_src, linksection_ref, "linksection must be comptime known");
|
||||
if (mem.indexOfScalar(u8, bytes, 0) != null) {
|
||||
return sema.fail(&block_scope, section_src, "linksection cannot contain null bytes", .{});
|
||||
} else if (bytes.len == 0) {
|
||||
return sema.fail(&block_scope, section_src, "linksection cannot be empty", .{});
|
||||
}
|
||||
break :blk (try decl_arena_allocator.dupeZ(u8, bytes)).ptr;
|
||||
};
|
||||
const target = sema.mod.getTarget();
|
||||
@ -4442,27 +4523,27 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
|
||||
.constant => target_util.defaultAddressSpace(target, .global_constant),
|
||||
else => unreachable,
|
||||
},
|
||||
else => |addrspace_ref| try sema.analyzeAddrspace(&block_scope, src, addrspace_ref, addrspace_ctx),
|
||||
else => |addrspace_ref| try sema.analyzeAddrspace(&block_scope, address_space_src, addrspace_ref, addrspace_ctx),
|
||||
};
|
||||
};
|
||||
|
||||
// Note this resolves the type of the Decl, not the value; if this Decl
|
||||
// is a struct, for example, this resolves `type` (which needs no resolution),
|
||||
// not the struct itself.
|
||||
try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty);
|
||||
try sema.resolveTypeLayout(&block_scope, ty_src, decl_tv.ty);
|
||||
|
||||
const decl_arena_state = try decl_arena_allocator.create(std.heap.ArenaAllocator.State);
|
||||
|
||||
if (decl.is_usingnamespace) {
|
||||
if (!decl_tv.ty.eql(Type.type, mod)) {
|
||||
return sema.fail(&block_scope, src, "expected type, found {}", .{
|
||||
return sema.fail(&block_scope, ty_src, "expected type, found {}", .{
|
||||
decl_tv.ty.fmt(mod),
|
||||
});
|
||||
}
|
||||
var buffer: Value.ToTypeBuffer = undefined;
|
||||
const ty = try decl_tv.val.toType(&buffer).copy(decl_arena_allocator);
|
||||
if (ty.getNamespace() == null) {
|
||||
return sema.fail(&block_scope, src, "type {} has no namespace", .{ty.fmt(mod)});
|
||||
return sema.fail(&block_scope, ty_src, "type {} has no namespace", .{ty.fmt(mod)});
|
||||
}
|
||||
|
||||
decl.ty = Type.type;
|
||||
@ -4508,7 +4589,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
|
||||
decl.analysis = .complete;
|
||||
decl.generation = mod.generation;
|
||||
|
||||
const has_runtime_bits = try sema.fnHasRuntimeBits(&block_scope, src, decl.ty);
|
||||
const has_runtime_bits = try sema.fnHasRuntimeBits(&block_scope, ty_src, decl.ty);
|
||||
|
||||
if (has_runtime_bits) {
|
||||
// We don't fully codegen the decl until later, but we do need to reserve a global
|
||||
@ -4525,7 +4606,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
|
||||
|
||||
const is_inline = decl.ty.fnCallingConvention() == .Inline;
|
||||
if (decl.is_exported) {
|
||||
const export_src = src; // TODO make this point at `export` token
|
||||
const export_src: LazySrcLoc = .{ .token_offset = @boolToInt(decl.is_pub) };
|
||||
if (is_inline) {
|
||||
return sema.fail(&block_scope, export_src, "export of inline function", .{});
|
||||
}
|
||||
@ -4588,14 +4669,14 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
|
||||
decl.generation = mod.generation;
|
||||
|
||||
const has_runtime_bits = is_extern or
|
||||
(queue_linker_work and try sema.typeHasRuntimeBits(&block_scope, src, decl.ty));
|
||||
(queue_linker_work and try sema.typeHasRuntimeBits(&block_scope, ty_src, decl.ty));
|
||||
|
||||
if (has_runtime_bits) {
|
||||
log.debug("queue linker work for {*} ({s})", .{ decl, decl.name });
|
||||
|
||||
// Needed for codegen_decl which will call updateDecl and then the
|
||||
// codegen backend wants full access to the Decl Type.
|
||||
try sema.resolveTypeFully(&block_scope, src, decl.ty);
|
||||
try sema.resolveTypeFully(&block_scope, ty_src, decl.ty);
|
||||
|
||||
try mod.comp.bin_file.allocateDeclIndexes(decl_index);
|
||||
try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });
|
||||
@ -4606,7 +4687,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
|
||||
}
|
||||
|
||||
if (decl.is_exported) {
|
||||
const export_src = src; // TODO point to the export token
|
||||
const export_src: LazySrcLoc = .{ .token_offset = @boolToInt(decl.is_pub) };
|
||||
// The scope needs to have the decl in it.
|
||||
const options: std.builtin.ExportOptions = .{ .name = mem.sliceTo(decl.name, 0) };
|
||||
try sema.analyzeExport(&block_scope, export_src, options, decl_index);
|
||||
|
||||
772
src/Sema.zig
772
src/Sema.zig
File diff suppressed because it is too large
Load Diff
@ -3548,6 +3548,12 @@ pub const Inst = struct {
|
||||
ty: Ref,
|
||||
init_count: u32,
|
||||
};
|
||||
|
||||
pub const Src = struct {
|
||||
node: i32,
|
||||
line: u32,
|
||||
column: u32,
|
||||
};
|
||||
};
|
||||
|
||||
pub const SpecialProng = enum { none, @"else", under };
|
||||
|
||||
@ -853,8 +853,7 @@ pub fn commitDeclState(
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_line_sect = &dwarf_segment.sections.items[d_sym.debug_line_section_index.?];
|
||||
const debug_line_sect = &d_sym.sections.items[d_sym.debug_line_section_index.?];
|
||||
const file_pos = debug_line_sect.offset + src_fn.off;
|
||||
try pwriteDbgLineNops(d_sym.file, file_pos, 0, &[0]u8{}, src_fn.len);
|
||||
},
|
||||
@ -933,8 +932,8 @@ pub fn commitDeclState(
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_line_sect = &dwarf_segment.sections.items[d_sym.debug_line_section_index.?];
|
||||
const dwarf_segment = d_sym.segments.items[d_sym.dwarf_segment_cmd_index.?];
|
||||
const debug_line_sect = &d_sym.sections.items[d_sym.debug_line_section_index.?];
|
||||
if (needed_size != debug_line_sect.size) {
|
||||
if (needed_size > d_sym.allocatedSize(debug_line_sect.offset)) {
|
||||
const new_offset = d_sym.findFreeSpace(needed_size, 1);
|
||||
@ -955,10 +954,9 @@ pub fn commitDeclState(
|
||||
);
|
||||
|
||||
debug_line_sect.offset = @intCast(u32, new_offset);
|
||||
debug_line_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff;
|
||||
debug_line_sect.addr = dwarf_segment.vmaddr + new_offset - dwarf_segment.fileoff;
|
||||
}
|
||||
debug_line_sect.size = needed_size;
|
||||
d_sym.load_commands_dirty = true; // TODO look into making only the one section dirty
|
||||
d_sym.debug_line_header_dirty = true;
|
||||
}
|
||||
const file_pos = debug_line_sect.offset + src_fn.off;
|
||||
@ -1137,8 +1135,7 @@ fn updateDeclDebugInfoAllocation(self: *Dwarf, file: *File, atom: *Atom, len: u3
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_info_sect = &dwarf_segment.sections.items[d_sym.debug_info_section_index.?];
|
||||
const debug_info_sect = &d_sym.sections.items[d_sym.debug_info_section_index.?];
|
||||
const file_pos = debug_info_sect.offset + atom.off;
|
||||
try pwriteDbgInfoNops(d_sym.file, file_pos, 0, &[0]u8{}, atom.len, false);
|
||||
},
|
||||
@ -1235,8 +1232,8 @@ fn writeDeclDebugInfo(self: *Dwarf, file: *File, atom: *Atom, dbg_info_buf: []co
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_info_sect = &dwarf_segment.sections.items[d_sym.debug_info_section_index.?];
|
||||
const dwarf_segment = d_sym.segments.items[d_sym.dwarf_segment_cmd_index.?];
|
||||
const debug_info_sect = &d_sym.sections.items[d_sym.debug_info_section_index.?];
|
||||
if (needed_size != debug_info_sect.size) {
|
||||
if (needed_size > d_sym.allocatedSize(debug_info_sect.offset)) {
|
||||
const new_offset = d_sym.findFreeSpace(needed_size, 1);
|
||||
@ -1257,10 +1254,9 @@ fn writeDeclDebugInfo(self: *Dwarf, file: *File, atom: *Atom, dbg_info_buf: []co
|
||||
);
|
||||
|
||||
debug_info_sect.offset = @intCast(u32, new_offset);
|
||||
debug_info_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff;
|
||||
debug_info_sect.addr = dwarf_segment.vmaddr + new_offset - dwarf_segment.fileoff;
|
||||
}
|
||||
debug_info_sect.size = needed_size;
|
||||
d_sym.load_commands_dirty = true; // TODO look into making only the one section dirty
|
||||
d_sym.debug_line_header_dirty = true;
|
||||
}
|
||||
const file_pos = debug_info_sect.offset + atom.off;
|
||||
@ -1330,8 +1326,7 @@ pub fn updateDeclLineNumber(self: *Dwarf, file: *File, decl: *const Module.Decl)
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = macho_file.d_sym.?;
|
||||
const dwarf_seg = d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const sect = dwarf_seg.sections.items[d_sym.debug_line_section_index.?];
|
||||
const sect = d_sym.sections.items[d_sym.debug_line_section_index.?];
|
||||
const file_pos = sect.offset + decl.fn_link.macho.off + self.getRelocDbgLineOff();
|
||||
try d_sym.file.pwriteAll(&data, file_pos);
|
||||
},
|
||||
@ -1557,14 +1552,14 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_abbrev_sect = &dwarf_segment.sections.items[d_sym.debug_abbrev_section_index.?];
|
||||
const dwarf_segment = d_sym.segments.items[d_sym.dwarf_segment_cmd_index.?];
|
||||
const debug_abbrev_sect = &d_sym.sections.items[d_sym.debug_abbrev_section_index.?];
|
||||
const allocated_size = d_sym.allocatedSize(debug_abbrev_sect.offset);
|
||||
if (needed_size > allocated_size) {
|
||||
debug_abbrev_sect.size = 0; // free the space
|
||||
const offset = d_sym.findFreeSpace(needed_size, 1);
|
||||
debug_abbrev_sect.offset = @intCast(u32, offset);
|
||||
debug_abbrev_sect.addr = dwarf_segment.inner.vmaddr + offset - dwarf_segment.inner.fileoff;
|
||||
debug_abbrev_sect.addr = dwarf_segment.vmaddr + offset - dwarf_segment.fileoff;
|
||||
}
|
||||
debug_abbrev_sect.size = needed_size;
|
||||
log.debug("__debug_abbrev start=0x{x} end=0x{x}", .{
|
||||
@ -1681,8 +1676,7 @@ pub fn writeDbgInfoHeader(self: *Dwarf, file: *File, module: *Module, low_pc: u6
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_seg = d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_info_sect = dwarf_seg.sections.items[d_sym.debug_info_section_index.?];
|
||||
const debug_info_sect = d_sym.sections.items[d_sym.debug_info_section_index.?];
|
||||
const file_pos = debug_info_sect.offset;
|
||||
try pwriteDbgInfoNops(d_sym.file, file_pos, 0, di_buf.items, jmp_amt, false);
|
||||
},
|
||||
@ -1998,13 +1992,13 @@ pub fn writeDbgAranges(self: *Dwarf, file: *File, addr: u64, size: u64) !void {
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_seg = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_aranges_sect = &dwarf_seg.sections.items[d_sym.debug_aranges_section_index.?];
|
||||
const dwarf_seg = d_sym.segments.items[d_sym.dwarf_segment_cmd_index.?];
|
||||
const debug_aranges_sect = &d_sym.sections.items[d_sym.debug_aranges_section_index.?];
|
||||
const allocated_size = d_sym.allocatedSize(debug_aranges_sect.offset);
|
||||
if (needed_size > allocated_size) {
|
||||
debug_aranges_sect.size = 0; // free the space
|
||||
const new_offset = d_sym.findFreeSpace(needed_size, 16);
|
||||
debug_aranges_sect.addr = dwarf_seg.inner.vmaddr + new_offset - dwarf_seg.inner.fileoff;
|
||||
debug_aranges_sect.addr = dwarf_seg.vmaddr + new_offset - dwarf_seg.fileoff;
|
||||
debug_aranges_sect.offset = @intCast(u32, new_offset);
|
||||
}
|
||||
debug_aranges_sect.size = needed_size;
|
||||
@ -2134,8 +2128,7 @@ pub fn writeDbgLineHeader(self: *Dwarf, file: *File, module: *Module) !void {
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_seg = d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_line_sect = dwarf_seg.sections.items[d_sym.debug_line_section_index.?];
|
||||
const debug_line_sect = d_sym.sections.items[d_sym.debug_line_section_index.?];
|
||||
const file_pos = debug_line_sect.offset;
|
||||
try pwriteDbgLineNops(d_sym.file, file_pos, 0, di_buf.items, jmp_amt);
|
||||
},
|
||||
@ -2264,8 +2257,7 @@ pub fn flushModule(self: *Dwarf, file: *File, module: *Module) !void {
|
||||
.macho => {
|
||||
const macho_file = file.cast(File.MachO).?;
|
||||
const d_sym = &macho_file.d_sym.?;
|
||||
const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_info_sect = &dwarf_segment.sections.items[d_sym.debug_info_section_index.?];
|
||||
const debug_info_sect = &d_sym.sections.items[d_sym.debug_info_section_index.?];
|
||||
break :blk debug_info_sect.offset;
|
||||
},
|
||||
// for wasm, the offset is always 0 as we write to memory first
|
||||
|
||||
4139
src/link/MachO.zig
4139
src/link/MachO.zig
File diff suppressed because it is too large
Load Diff
@ -6,19 +6,14 @@ const fs = std.fs;
|
||||
const log = std.log.scoped(.link);
|
||||
const macho = std.macho;
|
||||
const mem = std.mem;
|
||||
const fat = @import("fat.zig");
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
const Object = @import("Object.zig");
|
||||
|
||||
file: fs.File,
|
||||
fat_offset: u64,
|
||||
name: []const u8,
|
||||
|
||||
header: ?ar_hdr = null,
|
||||
|
||||
// The actual contents we care about linking with will be embedded at
|
||||
// an offset within a file if we are linking against a fat lib
|
||||
library_offset: u64 = 0,
|
||||
header: ar_hdr = undefined,
|
||||
|
||||
/// Parsed table of contents.
|
||||
/// Each symbol name points to a list of all definition
|
||||
@ -103,11 +98,7 @@ pub fn deinit(self: *Archive, allocator: Allocator) void {
|
||||
allocator.free(self.name);
|
||||
}
|
||||
|
||||
pub fn parse(self: *Archive, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch) !void {
|
||||
const reader = self.file.reader();
|
||||
self.library_offset = try fat.getLibraryOffset(reader, cpu_arch);
|
||||
try self.file.seekTo(self.library_offset);
|
||||
|
||||
pub fn parse(self: *Archive, allocator: Allocator, reader: anytype) !void {
|
||||
const magic = try reader.readBytesNoEof(SARMAG);
|
||||
if (!mem.eql(u8, &magic, ARMAG)) {
|
||||
log.debug("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic });
|
||||
@ -115,21 +106,23 @@ pub fn parse(self: *Archive, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch
|
||||
}
|
||||
|
||||
self.header = try reader.readStruct(ar_hdr);
|
||||
if (!mem.eql(u8, &self.header.?.ar_fmag, ARFMAG)) {
|
||||
log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, self.header.?.ar_fmag });
|
||||
if (!mem.eql(u8, &self.header.ar_fmag, ARFMAG)) {
|
||||
log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{
|
||||
ARFMAG,
|
||||
self.header.ar_fmag,
|
||||
});
|
||||
return error.NotArchive;
|
||||
}
|
||||
|
||||
var embedded_name = try parseName(allocator, self.header.?, reader);
|
||||
const name_or_length = try self.header.nameOrLength();
|
||||
var embedded_name = try parseName(allocator, name_or_length, reader);
|
||||
log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, self.name });
|
||||
defer allocator.free(embedded_name);
|
||||
|
||||
try self.parseTableOfContents(allocator, reader);
|
||||
try reader.context.seekTo(0);
|
||||
}
|
||||
|
||||
fn parseName(allocator: Allocator, header: ar_hdr, reader: anytype) ![]u8 {
|
||||
const name_or_length = try header.nameOrLength();
|
||||
fn parseName(allocator: Allocator, name_or_length: ar_hdr.NameOrLength, reader: anytype) ![]u8 {
|
||||
var name: []u8 = undefined;
|
||||
switch (name_or_length) {
|
||||
.Name => |n| {
|
||||
@ -187,9 +180,14 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parseObject(self: Archive, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, offset: u32) !Object {
|
||||
pub fn parseObject(
|
||||
self: Archive,
|
||||
allocator: Allocator,
|
||||
cpu_arch: std.Target.Cpu.Arch,
|
||||
offset: u32,
|
||||
) !Object {
|
||||
const reader = self.file.reader();
|
||||
try reader.context.seekTo(offset + self.library_offset);
|
||||
try reader.context.seekTo(self.fat_offset + offset);
|
||||
|
||||
const object_header = try reader.readStruct(ar_hdr);
|
||||
|
||||
@ -198,7 +196,8 @@ pub fn parseObject(self: Archive, allocator: Allocator, cpu_arch: std.Target.Cpu
|
||||
return error.MalformedArchive;
|
||||
}
|
||||
|
||||
const object_name = try parseName(allocator, object_header, reader);
|
||||
const name_or_length = try object_header.nameOrLength();
|
||||
const object_name = try parseName(allocator, name_or_length, reader);
|
||||
defer allocator.free(object_name);
|
||||
|
||||
log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name });
|
||||
@ -209,15 +208,24 @@ pub fn parseObject(self: Archive, allocator: Allocator, cpu_arch: std.Target.Cpu
|
||||
break :name try std.fmt.allocPrint(allocator, "{s}({s})", .{ path, object_name });
|
||||
};
|
||||
|
||||
const object_name_len = switch (name_or_length) {
|
||||
.Name => 0,
|
||||
.Length => |len| len,
|
||||
};
|
||||
const object_size = (try object_header.size()) - object_name_len;
|
||||
const contents = try allocator.allocWithOptions(u8, object_size, @alignOf(u64), null);
|
||||
const amt = try reader.readAll(contents);
|
||||
if (amt != object_size) {
|
||||
return error.InputOutput;
|
||||
}
|
||||
|
||||
var object = Object{
|
||||
.file = try fs.cwd().openFile(self.name, .{}),
|
||||
.name = name,
|
||||
.file_offset = @intCast(u32, try reader.context.getPos()),
|
||||
.mtime = try self.header.?.date(),
|
||||
.mtime = try self.header.date(),
|
||||
.contents = contents,
|
||||
};
|
||||
|
||||
try object.parse(allocator, cpu_arch);
|
||||
try reader.context.seekTo(0);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
@ -246,7 +246,7 @@ pub fn parseRelocs(self: *Atom, relocs: []const macho.relocation_info, context:
|
||||
else => {
|
||||
log.err("unexpected relocation type after ARM64_RELOC_ADDEND", .{});
|
||||
log.err(" expected ARM64_RELOC_PAGE21 or ARM64_RELOC_PAGEOFF12", .{});
|
||||
log.err(" found {}", .{next});
|
||||
log.err(" found {s}", .{@tagName(next)});
|
||||
return error.UnexpectedRelocationType;
|
||||
},
|
||||
}
|
||||
@ -285,7 +285,9 @@ pub fn parseRelocs(self: *Atom, relocs: []const macho.relocation_info, context:
|
||||
else => {
|
||||
log.err("unexpected relocation type after ARM64_RELOC_ADDEND", .{});
|
||||
log.err(" expected ARM64_RELOC_UNSIGNED", .{});
|
||||
log.err(" found {}", .{@intToEnum(macho.reloc_type_arm64, relocs[i + 1].r_type)});
|
||||
log.err(" found {s}", .{
|
||||
@tagName(@intToEnum(macho.reloc_type_arm64, relocs[i + 1].r_type)),
|
||||
});
|
||||
return error.UnexpectedRelocationType;
|
||||
},
|
||||
},
|
||||
@ -294,7 +296,9 @@ pub fn parseRelocs(self: *Atom, relocs: []const macho.relocation_info, context:
|
||||
else => {
|
||||
log.err("unexpected relocation type after X86_64_RELOC_ADDEND", .{});
|
||||
log.err(" expected X86_64_RELOC_UNSIGNED", .{});
|
||||
log.err(" found {}", .{@intToEnum(macho.reloc_type_x86_64, relocs[i + 1].r_type)});
|
||||
log.err(" found {s}", .{
|
||||
@tagName(@intToEnum(macho.reloc_type_x86_64, relocs[i + 1].r_type)),
|
||||
});
|
||||
return error.UnexpectedRelocationType;
|
||||
},
|
||||
},
|
||||
@ -309,13 +313,13 @@ pub fn parseRelocs(self: *Atom, relocs: []const macho.relocation_info, context:
|
||||
const sect_id = @intCast(u16, rel.r_symbolnum - 1);
|
||||
const sym_index = object.sections_as_symbols.get(sect_id) orelse blk: {
|
||||
const sect = object.getSourceSection(sect_id);
|
||||
const match = (try context.macho_file.getMatchingSection(sect)) orelse
|
||||
const match = (try context.macho_file.getOutputSection(sect)) orelse
|
||||
unreachable;
|
||||
const sym_index = @intCast(u32, object.symtab.items.len);
|
||||
try object.symtab.append(gpa, .{
|
||||
.n_strx = 0,
|
||||
.n_type = macho.N_SECT,
|
||||
.n_sect = context.macho_file.getSectionOrdinal(match),
|
||||
.n_sect = match + 1,
|
||||
.n_desc = 0,
|
||||
.n_value = sect.addr,
|
||||
});
|
||||
@ -459,9 +463,10 @@ fn addPtrBindingOrRebase(
|
||||
});
|
||||
} else {
|
||||
const source_sym = self.getSymbol(context.macho_file);
|
||||
const match = context.macho_file.getMatchingSectionFromOrdinal(source_sym.n_sect);
|
||||
const sect = context.macho_file.getSection(match);
|
||||
const sect_type = sect.type_();
|
||||
const section = context.macho_file.sections.get(source_sym.n_sect - 1);
|
||||
const header = section.header;
|
||||
const segment_index = section.segment_index;
|
||||
const sect_type = header.@"type"();
|
||||
|
||||
const should_rebase = rebase: {
|
||||
if (rel.r_length != 3) break :rebase false;
|
||||
@ -470,12 +475,12 @@ fn addPtrBindingOrRebase(
|
||||
// that the segment is writable should be enough here.
|
||||
const is_right_segment = blk: {
|
||||
if (context.macho_file.data_segment_cmd_index) |idx| {
|
||||
if (match.seg == idx) {
|
||||
if (segment_index == idx) {
|
||||
break :blk true;
|
||||
}
|
||||
}
|
||||
if (context.macho_file.data_const_segment_cmd_index) |idx| {
|
||||
if (match.seg == idx) {
|
||||
if (segment_index == idx) {
|
||||
break :blk true;
|
||||
}
|
||||
}
|
||||
@ -524,6 +529,7 @@ fn addStub(target: MachO.SymbolWithLoc, context: RelocContext) !void {
|
||||
if (context.macho_file.stubs_table.contains(target)) return;
|
||||
|
||||
const stub_index = try context.macho_file.allocateStubEntry(target);
|
||||
|
||||
const stub_helper_atom = try context.macho_file.createStubHelperAtom();
|
||||
const laptr_atom = try context.macho_file.createLazyPointerAtom(stub_helper_atom.sym_index, target);
|
||||
const stub_atom = try context.macho_file.createStubAtom(laptr_atom.sym_index);
|
||||
@ -565,9 +571,8 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
|
||||
};
|
||||
const is_tlv = is_tlv: {
|
||||
const source_sym = self.getSymbol(macho_file);
|
||||
const match = macho_file.getMatchingSectionFromOrdinal(source_sym.n_sect);
|
||||
const sect = macho_file.getSection(match);
|
||||
break :is_tlv sect.type_() == macho.S_THREAD_LOCAL_VARIABLES;
|
||||
const header = macho_file.sections.items(.header)[source_sym.n_sect - 1];
|
||||
break :is_tlv header.@"type"() == macho.S_THREAD_LOCAL_VARIABLES;
|
||||
};
|
||||
const target_addr = blk: {
|
||||
const target_atom = rel.getTargetAtom(macho_file) orelse {
|
||||
@ -597,9 +602,9 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
|
||||
// * wrt to __thread_data if defined, then
|
||||
// * wrt to __thread_bss
|
||||
const sect_id: u16 = sect_id: {
|
||||
if (macho_file.tlv_data_section_index) |i| {
|
||||
if (macho_file.getSectionByName("__DATA", "__thread_data")) |i| {
|
||||
break :sect_id i;
|
||||
} else if (macho_file.tlv_bss_section_index) |i| {
|
||||
} else if (macho_file.getSectionByName("__DATA", "__thread_bss")) |i| {
|
||||
break :sect_id i;
|
||||
} else {
|
||||
log.err("threadlocal variables present but no initializer sections found", .{});
|
||||
@ -608,10 +613,7 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
|
||||
return error.FailedToResolveRelocationTarget;
|
||||
}
|
||||
};
|
||||
break :base_address macho_file.getSection(.{
|
||||
.seg = macho_file.data_segment_cmd_index.?,
|
||||
.sect = sect_id,
|
||||
}).addr;
|
||||
break :base_address macho_file.sections.items(.header)[sect_id].addr;
|
||||
} else 0;
|
||||
break :blk target_sym.n_value - base_address;
|
||||
};
|
||||
|
||||
@ -252,7 +252,7 @@ pub const WriteOpts = struct {
|
||||
file: fs.File,
|
||||
exec_seg_base: u64,
|
||||
exec_seg_limit: u64,
|
||||
code_sig_cmd: macho.linkedit_data_command,
|
||||
file_size: u32,
|
||||
output_mode: std.builtin.OutputMode,
|
||||
};
|
||||
|
||||
@ -274,10 +274,9 @@ pub fn writeAdhocSignature(
|
||||
self.code_directory.inner.execSegBase = opts.exec_seg_base;
|
||||
self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
|
||||
self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
|
||||
const file_size = opts.code_sig_cmd.dataoff;
|
||||
self.code_directory.inner.codeLimit = file_size;
|
||||
self.code_directory.inner.codeLimit = opts.file_size;
|
||||
|
||||
const total_pages = mem.alignForward(file_size, self.page_size) / self.page_size;
|
||||
const total_pages = mem.alignForward(opts.file_size, self.page_size) / self.page_size;
|
||||
|
||||
var buffer = try allocator.alloc(u8, self.page_size);
|
||||
defer allocator.free(buffer);
|
||||
@ -289,7 +288,10 @@ pub fn writeAdhocSignature(
|
||||
var i: usize = 0;
|
||||
while (i < total_pages) : (i += 1) {
|
||||
const fstart = i * self.page_size;
|
||||
const fsize = if (fstart + self.page_size > file_size) file_size - fstart else self.page_size;
|
||||
const fsize = if (fstart + self.page_size > opts.file_size)
|
||||
opts.file_size - fstart
|
||||
else
|
||||
self.page_size;
|
||||
const len = try opts.file.preadAll(buffer, fstart);
|
||||
assert(fsize <= len);
|
||||
|
||||
|
||||
@ -25,35 +25,18 @@ base: *MachO,
|
||||
dwarf: Dwarf,
|
||||
file: fs.File,
|
||||
|
||||
/// Table of all load commands
|
||||
load_commands: std.ArrayListUnmanaged(macho.LoadCommand) = .{},
|
||||
/// __PAGEZERO segment
|
||||
pagezero_segment_cmd_index: ?u16 = null,
|
||||
/// __TEXT segment
|
||||
text_segment_cmd_index: ?u16 = null,
|
||||
/// __DATA_CONST segment
|
||||
data_const_segment_cmd_index: ?u16 = null,
|
||||
/// __DATA segment
|
||||
data_segment_cmd_index: ?u16 = null,
|
||||
/// __LINKEDIT segment
|
||||
linkedit_segment_cmd_index: ?u16 = null,
|
||||
/// __DWARF segment
|
||||
dwarf_segment_cmd_index: ?u16 = null,
|
||||
/// Symbol table
|
||||
symtab_cmd_index: ?u16 = null,
|
||||
/// UUID load command
|
||||
uuid_cmd_index: ?u16 = null,
|
||||
segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
|
||||
sections: std.ArrayListUnmanaged(macho.section_64) = .{},
|
||||
|
||||
/// Index into __TEXT,__text section.
|
||||
text_section_index: ?u16 = null,
|
||||
linkedit_segment_cmd_index: ?u8 = null,
|
||||
dwarf_segment_cmd_index: ?u8 = null,
|
||||
|
||||
debug_info_section_index: ?u16 = null,
|
||||
debug_abbrev_section_index: ?u16 = null,
|
||||
debug_str_section_index: ?u16 = null,
|
||||
debug_aranges_section_index: ?u16 = null,
|
||||
debug_line_section_index: ?u16 = null,
|
||||
debug_info_section_index: ?u8 = null,
|
||||
debug_abbrev_section_index: ?u8 = null,
|
||||
debug_str_section_index: ?u8 = null,
|
||||
debug_aranges_section_index: ?u8 = null,
|
||||
debug_line_section_index: ?u8 = null,
|
||||
|
||||
load_commands_dirty: bool = false,
|
||||
debug_string_table_dirty: bool = false,
|
||||
debug_abbrev_section_dirty: bool = false,
|
||||
debug_aranges_section_dirty: bool = false,
|
||||
@ -78,98 +61,44 @@ pub const Reloc = struct {
|
||||
/// You must call this function *after* `MachO.populateMissingMetadata()`
|
||||
/// has been called to get a viable debug symbols output.
|
||||
pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void {
|
||||
if (self.uuid_cmd_index == null) {
|
||||
const base_cmd = self.base.load_commands.items[self.base.uuid_cmd_index.?];
|
||||
self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
try self.load_commands.append(allocator, base_cmd);
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
if (self.symtab_cmd_index == null) {
|
||||
self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
try self.load_commands.append(self.base.base.allocator, .{
|
||||
.symtab = .{
|
||||
.cmdsize = @sizeOf(macho.symtab_command),
|
||||
.symoff = 0,
|
||||
.nsyms = 0,
|
||||
.stroff = 0,
|
||||
.strsize = 0,
|
||||
},
|
||||
});
|
||||
try self.strtab.buffer.append(allocator, 0);
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
if (self.pagezero_segment_cmd_index == null) {
|
||||
self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
const base_cmd = self.base.load_commands.items[self.base.pagezero_segment_cmd_index.?].segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
try self.load_commands.append(allocator, .{ .segment = cmd });
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
if (self.text_segment_cmd_index == null) {
|
||||
self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
const base_cmd = self.base.load_commands.items[self.base.text_segment_cmd_index.?].segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
try self.load_commands.append(allocator, .{ .segment = cmd });
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
if (self.data_const_segment_cmd_index == null) outer: {
|
||||
if (self.base.data_const_segment_cmd_index == null) break :outer; // __DATA_CONST is optional
|
||||
self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
const base_cmd = self.base.load_commands.items[self.base.data_const_segment_cmd_index.?].segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
try self.load_commands.append(allocator, .{ .segment = cmd });
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
if (self.data_segment_cmd_index == null) outer: {
|
||||
if (self.base.data_segment_cmd_index == null) break :outer; // __DATA is optional
|
||||
self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
const base_cmd = self.base.load_commands.items[self.base.data_segment_cmd_index.?].segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
try self.load_commands.append(allocator, .{ .segment = cmd });
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
if (self.linkedit_segment_cmd_index == null) {
|
||||
self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
const base_cmd = self.base.load_commands.items[self.base.linkedit_segment_cmd_index.?].segment;
|
||||
var cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
self.linkedit_segment_cmd_index = @intCast(u8, self.segments.items.len);
|
||||
log.debug("found __LINKEDIT segment free space 0x{x} to 0x{x}", .{
|
||||
self.base.page_size,
|
||||
self.base.page_size * 2,
|
||||
});
|
||||
// TODO this needs reworking
|
||||
cmd.inner.vmsize = self.base.page_size;
|
||||
cmd.inner.fileoff = self.base.page_size;
|
||||
cmd.inner.filesize = self.base.page_size;
|
||||
try self.load_commands.append(allocator, .{ .segment = cmd });
|
||||
self.load_commands_dirty = true;
|
||||
try self.segments.append(allocator, .{
|
||||
.segname = makeStaticString("__LINKEDIT"),
|
||||
.vmaddr = self.base.page_size,
|
||||
.vmsize = self.base.page_size,
|
||||
.fileoff = self.base.page_size,
|
||||
.filesize = self.base.page_size,
|
||||
.maxprot = macho.PROT.READ,
|
||||
.initprot = macho.PROT.READ,
|
||||
.cmdsize = @sizeOf(macho.segment_command_64),
|
||||
});
|
||||
}
|
||||
|
||||
if (self.dwarf_segment_cmd_index == null) {
|
||||
self.dwarf_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
self.dwarf_segment_cmd_index = @intCast(u8, self.segments.items.len);
|
||||
|
||||
const linkedit = self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
|
||||
const linkedit = self.segments.items[self.linkedit_segment_cmd_index.?];
|
||||
const ideal_size: u16 = 200 + 128 + 160 + 250;
|
||||
const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), self.base.page_size);
|
||||
const fileoff = linkedit.inner.fileoff + linkedit.inner.filesize;
|
||||
const vmaddr = linkedit.inner.vmaddr + linkedit.inner.vmsize;
|
||||
const fileoff = linkedit.fileoff + linkedit.filesize;
|
||||
const vmaddr = linkedit.vmaddr + linkedit.vmsize;
|
||||
|
||||
log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size });
|
||||
|
||||
try self.load_commands.append(allocator, .{
|
||||
.segment = .{
|
||||
.inner = .{
|
||||
.segname = makeStaticString("__DWARF"),
|
||||
.vmaddr = vmaddr,
|
||||
.vmsize = needed_size,
|
||||
.fileoff = fileoff,
|
||||
.filesize = needed_size,
|
||||
.cmdsize = @sizeOf(macho.segment_command_64),
|
||||
},
|
||||
},
|
||||
try self.segments.append(allocator, .{
|
||||
.segname = makeStaticString("__DWARF"),
|
||||
.vmaddr = vmaddr,
|
||||
.vmsize = needed_size,
|
||||
.fileoff = fileoff,
|
||||
.filesize = needed_size,
|
||||
.cmdsize = @sizeOf(macho.segment_command_64),
|
||||
});
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
if (self.debug_str_section_index == null) {
|
||||
@ -203,18 +132,18 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
|
||||
}
|
||||
}
|
||||
|
||||
fn allocateSection(self: *DebugSymbols, sectname: []const u8, size: u64, alignment: u16) !u16 {
|
||||
const seg = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
fn allocateSection(self: *DebugSymbols, sectname: []const u8, size: u64, alignment: u16) !u8 {
|
||||
const segment = &self.segments.items[self.dwarf_segment_cmd_index.?];
|
||||
var sect = macho.section_64{
|
||||
.sectname = makeStaticString(sectname),
|
||||
.segname = seg.inner.segname,
|
||||
.segname = segment.segname,
|
||||
.size = @intCast(u32, size),
|
||||
.@"align" = alignment,
|
||||
};
|
||||
const alignment_pow_2 = try math.powi(u32, 2, alignment);
|
||||
const off = self.findFreeSpace(size, alignment_pow_2);
|
||||
|
||||
assert(off + size <= seg.inner.fileoff + seg.inner.filesize); // TODO expand
|
||||
assert(off + size <= segment.fileoff + segment.filesize); // TODO expand
|
||||
|
||||
log.debug("found {s},{s} section free space 0x{x} to 0x{x}", .{
|
||||
sect.segName(),
|
||||
@ -223,31 +152,20 @@ fn allocateSection(self: *DebugSymbols, sectname: []const u8, size: u64, alignme
|
||||
off + size,
|
||||
});
|
||||
|
||||
sect.addr = seg.inner.vmaddr + off - seg.inner.fileoff;
|
||||
sect.addr = segment.vmaddr + off - segment.fileoff;
|
||||
sect.offset = @intCast(u32, off);
|
||||
|
||||
const index = @intCast(u16, seg.sections.items.len);
|
||||
try seg.sections.append(self.base.base.allocator, sect);
|
||||
seg.inner.cmdsize += @sizeOf(macho.section_64);
|
||||
seg.inner.nsects += 1;
|
||||
|
||||
// TODO
|
||||
// const match = MatchingSection{
|
||||
// .seg = segment_id,
|
||||
// .sect = index,
|
||||
// };
|
||||
// _ = try self.section_ordinals.getOrPut(self.base.allocator, match);
|
||||
// try self.block_free_lists.putNoClobber(self.base.allocator, match, .{});
|
||||
|
||||
self.load_commands_dirty = true;
|
||||
const index = @intCast(u8, self.sections.items.len);
|
||||
try self.sections.append(self.base.base.allocator, sect);
|
||||
segment.cmdsize += @sizeOf(macho.section_64);
|
||||
segment.nsects += 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) ?u64 {
|
||||
const seg = self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
const end = start + padToIdeal(size);
|
||||
for (seg.sections.items) |section| {
|
||||
for (self.sections.items) |section| {
|
||||
const increased_size = padToIdeal(section.size);
|
||||
const test_end = section.offset + increased_size;
|
||||
if (end > section.offset and start < test_end) {
|
||||
@ -258,8 +176,8 @@ fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) ?u64 {
|
||||
}
|
||||
|
||||
pub fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) u64 {
|
||||
const seg = self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
var offset: u64 = seg.inner.fileoff;
|
||||
const segment = self.segments.items[self.dwarf_segment_cmd_index.?];
|
||||
var offset: u64 = segment.fileoff;
|
||||
while (self.detectAllocCollision(offset, object_size)) |item_end| {
|
||||
offset = mem.alignForwardGeneric(u64, item_end, min_alignment);
|
||||
}
|
||||
@ -296,8 +214,7 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
|
||||
break :blk got_entry.getName(self.base);
|
||||
},
|
||||
};
|
||||
const seg = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
const sect = &seg.sections.items[self.debug_info_section_index.?];
|
||||
const sect = &self.sections.items[self.debug_info_section_index.?];
|
||||
const file_offset = sect.offset + reloc.offset;
|
||||
log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{
|
||||
reloc.target,
|
||||
@ -311,15 +228,13 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
|
||||
|
||||
if (self.debug_abbrev_section_dirty) {
|
||||
try self.dwarf.writeDbgAbbrev(&self.base.base);
|
||||
self.load_commands_dirty = true;
|
||||
self.debug_abbrev_section_dirty = false;
|
||||
}
|
||||
|
||||
if (self.debug_info_header_dirty) {
|
||||
// Currently only one compilation unit is supported, so the address range is simply
|
||||
// identical to the main program header virtual address and memory size.
|
||||
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment;
|
||||
const text_section = text_segment.sections.items[self.text_section_index.?];
|
||||
const text_section = self.base.sections.items(.header)[self.base.text_section_index.?];
|
||||
const low_pc = text_section.addr;
|
||||
const high_pc = text_section.addr + text_section.size;
|
||||
try self.dwarf.writeDbgInfoHeader(&self.base.base, module, low_pc, high_pc);
|
||||
@ -329,10 +244,8 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
|
||||
if (self.debug_aranges_section_dirty) {
|
||||
// Currently only one compilation unit is supported, so the address range is simply
|
||||
// identical to the main program header virtual address and memory size.
|
||||
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment;
|
||||
const text_section = text_segment.sections.items[self.text_section_index.?];
|
||||
const text_section = self.base.sections.items(.header)[self.base.text_section_index.?];
|
||||
try self.dwarf.writeDbgAranges(&self.base.base, text_section.addr, text_section.size);
|
||||
self.load_commands_dirty = true;
|
||||
self.debug_aranges_section_dirty = false;
|
||||
}
|
||||
|
||||
@ -342,8 +255,8 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
|
||||
}
|
||||
|
||||
{
|
||||
const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
const debug_strtab_sect = &dwarf_segment.sections.items[self.debug_str_section_index.?];
|
||||
const dwarf_segment = &self.segments.items[self.dwarf_segment_cmd_index.?];
|
||||
const debug_strtab_sect = &self.sections.items[self.debug_str_section_index.?];
|
||||
if (self.debug_string_table_dirty or self.dwarf.strtab.items.len != debug_strtab_sect.size) {
|
||||
const allocated_size = self.allocatedSize(debug_strtab_sect.offset);
|
||||
const needed_size = self.dwarf.strtab.items.len;
|
||||
@ -351,7 +264,7 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
|
||||
if (needed_size > allocated_size) {
|
||||
debug_strtab_sect.size = 0; // free the space
|
||||
const new_offset = self.findFreeSpace(needed_size, 1);
|
||||
debug_strtab_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff;
|
||||
debug_strtab_sect.addr = dwarf_segment.vmaddr + new_offset - dwarf_segment.fileoff;
|
||||
debug_strtab_sect.offset = @intCast(u32, new_offset);
|
||||
}
|
||||
debug_strtab_sect.size = @intCast(u32, needed_size);
|
||||
@ -362,28 +275,40 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
|
||||
});
|
||||
|
||||
try self.file.pwriteAll(self.dwarf.strtab.items, debug_strtab_sect.offset);
|
||||
self.load_commands_dirty = true;
|
||||
self.debug_string_table_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
self.updateDwarfSegment();
|
||||
try self.writeLinkeditSegment();
|
||||
try self.updateVirtualMemoryMapping();
|
||||
try self.writeLoadCommands(allocator);
|
||||
try self.writeHeader();
|
||||
var lc_buffer = std.ArrayList(u8).init(allocator);
|
||||
defer lc_buffer.deinit();
|
||||
const lc_writer = lc_buffer.writer();
|
||||
var ncmds: u32 = 0;
|
||||
|
||||
try self.writeLinkeditSegmentData(&ncmds, lc_writer);
|
||||
self.updateDwarfSegment();
|
||||
|
||||
{
|
||||
try lc_writer.writeStruct(self.base.uuid);
|
||||
ncmds += 1;
|
||||
}
|
||||
|
||||
var headers_buf = std.ArrayList(u8).init(allocator);
|
||||
defer headers_buf.deinit();
|
||||
try self.writeSegmentHeaders(&ncmds, headers_buf.writer());
|
||||
|
||||
try self.file.pwriteAll(headers_buf.items, @sizeOf(macho.mach_header_64));
|
||||
try self.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64) + headers_buf.items.len);
|
||||
|
||||
try self.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len + headers_buf.items.len));
|
||||
|
||||
assert(!self.load_commands_dirty);
|
||||
assert(!self.debug_abbrev_section_dirty);
|
||||
assert(!self.debug_aranges_section_dirty);
|
||||
assert(!self.debug_string_table_dirty);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DebugSymbols, allocator: Allocator) void {
|
||||
for (self.load_commands.items) |*lc| {
|
||||
lc.deinit(allocator);
|
||||
}
|
||||
self.load_commands.deinit(allocator);
|
||||
self.segments.deinit(allocator);
|
||||
self.sections.deinit(allocator);
|
||||
self.dwarf.deinit();
|
||||
self.strtab.deinit(allocator);
|
||||
self.relocs.deinit(allocator);
|
||||
@ -402,59 +327,20 @@ pub fn swapRemoveRelocs(self: *DebugSymbols, target: u32) void {
|
||||
}
|
||||
}
|
||||
|
||||
fn copySegmentCommand(
|
||||
self: *DebugSymbols,
|
||||
allocator: Allocator,
|
||||
base_cmd: macho.SegmentCommand,
|
||||
) !macho.SegmentCommand {
|
||||
var cmd = macho.SegmentCommand{
|
||||
.inner = .{
|
||||
.segname = undefined,
|
||||
.cmdsize = base_cmd.inner.cmdsize,
|
||||
.vmaddr = base_cmd.inner.vmaddr,
|
||||
.vmsize = base_cmd.inner.vmsize,
|
||||
.maxprot = base_cmd.inner.maxprot,
|
||||
.initprot = base_cmd.inner.initprot,
|
||||
.nsects = base_cmd.inner.nsects,
|
||||
.flags = base_cmd.inner.flags,
|
||||
},
|
||||
};
|
||||
mem.copy(u8, &cmd.inner.segname, &base_cmd.inner.segname);
|
||||
fn updateDwarfSegment(self: *DebugSymbols) void {
|
||||
const linkedit = self.segments.items[self.linkedit_segment_cmd_index.?];
|
||||
const dwarf_segment = &self.segments.items[self.dwarf_segment_cmd_index.?];
|
||||
|
||||
try cmd.sections.ensureTotalCapacity(allocator, cmd.inner.nsects);
|
||||
for (base_cmd.sections.items) |base_sect, i| {
|
||||
var sect = macho.section_64{
|
||||
.sectname = undefined,
|
||||
.segname = undefined,
|
||||
.addr = base_sect.addr,
|
||||
.size = base_sect.size,
|
||||
.offset = 0,
|
||||
.@"align" = base_sect.@"align",
|
||||
.reloff = 0,
|
||||
.nreloc = 0,
|
||||
.flags = base_sect.flags,
|
||||
.reserved1 = base_sect.reserved1,
|
||||
.reserved2 = base_sect.reserved2,
|
||||
.reserved3 = base_sect.reserved3,
|
||||
};
|
||||
mem.copy(u8, §.sectname, &base_sect.sectname);
|
||||
mem.copy(u8, §.segname, &base_sect.segname);
|
||||
|
||||
if (self.base.text_section_index.? == i) {
|
||||
self.text_section_index = @intCast(u16, i);
|
||||
}
|
||||
|
||||
cmd.sections.appendAssumeCapacity(sect);
|
||||
const new_start_aligned = linkedit.vmaddr + linkedit.vmsize;
|
||||
const old_start_aligned = dwarf_segment.vmaddr;
|
||||
const diff = new_start_aligned - old_start_aligned;
|
||||
if (diff > 0) {
|
||||
dwarf_segment.vmaddr = new_start_aligned;
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
fn updateDwarfSegment(self: *DebugSymbols) void {
|
||||
const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
|
||||
var max_offset: u64 = 0;
|
||||
for (dwarf_segment.sections.items) |sect| {
|
||||
for (self.sections.items) |*sect| {
|
||||
sect.addr += diff;
|
||||
log.debug(" {s},{s} - 0x{x}-0x{x} - 0x{x}-0x{x}", .{
|
||||
sect.segName(),
|
||||
sect.sectName(),
|
||||
@ -468,42 +354,59 @@ fn updateDwarfSegment(self: *DebugSymbols) void {
|
||||
}
|
||||
}
|
||||
|
||||
const file_size = max_offset - dwarf_segment.inner.fileoff;
|
||||
const file_size = max_offset - dwarf_segment.fileoff;
|
||||
log.debug("__DWARF size 0x{x}", .{file_size});
|
||||
|
||||
if (file_size != dwarf_segment.inner.filesize) {
|
||||
dwarf_segment.inner.filesize = file_size;
|
||||
if (dwarf_segment.inner.vmsize < dwarf_segment.inner.filesize) {
|
||||
dwarf_segment.inner.vmsize = mem.alignForwardGeneric(u64, dwarf_segment.inner.filesize, self.base.page_size);
|
||||
if (file_size != dwarf_segment.filesize) {
|
||||
dwarf_segment.filesize = file_size;
|
||||
dwarf_segment.vmsize = mem.alignForwardGeneric(u64, dwarf_segment.filesize, self.base.page_size);
|
||||
}
|
||||
}
|
||||
|
||||
fn writeSegmentHeaders(self: *DebugSymbols, ncmds: *u32, writer: anytype) !void {
|
||||
// Write segment/section headers from the binary file first.
|
||||
const end = self.base.linkedit_segment_cmd_index.?;
|
||||
for (self.base.segments.items[0..end]) |seg, i| {
|
||||
const indexes = self.base.getSectionIndexes(@intCast(u8, i));
|
||||
var out_seg = seg;
|
||||
out_seg.fileoff = 0;
|
||||
out_seg.filesize = 0;
|
||||
out_seg.cmdsize = @sizeOf(macho.segment_command_64);
|
||||
out_seg.nsects = 0;
|
||||
|
||||
// Update section headers count; any section with size of 0 is excluded
|
||||
// since it doesn't have any data in the final binary file.
|
||||
for (self.base.sections.items(.header)[indexes.start..indexes.end]) |header| {
|
||||
if (header.size == 0) continue;
|
||||
out_seg.cmdsize += @sizeOf(macho.section_64);
|
||||
out_seg.nsects += 1;
|
||||
}
|
||||
self.load_commands_dirty = true;
|
||||
|
||||
if (out_seg.nsects == 0 and
|
||||
(mem.eql(u8, out_seg.segName(), "__DATA_CONST") or
|
||||
mem.eql(u8, out_seg.segName(), "__DATA"))) continue;
|
||||
|
||||
try writer.writeStruct(out_seg);
|
||||
for (self.base.sections.items(.header)[indexes.start..indexes.end]) |header| {
|
||||
if (header.size == 0) continue;
|
||||
var out_header = header;
|
||||
out_header.offset = 0;
|
||||
try writer.writeStruct(out_header);
|
||||
}
|
||||
|
||||
ncmds.* += 1;
|
||||
}
|
||||
// Next, commit DSYM's __LINKEDIT and __DWARF segments headers.
|
||||
for (self.segments.items) |seg| {
|
||||
try writer.writeStruct(seg);
|
||||
ncmds.* += 1;
|
||||
}
|
||||
for (self.sections.items) |header| {
|
||||
try writer.writeStruct(header);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes all load commands and section headers.
|
||||
fn writeLoadCommands(self: *DebugSymbols, allocator: Allocator) !void {
|
||||
if (!self.load_commands_dirty) return;
|
||||
|
||||
var sizeofcmds: u32 = 0;
|
||||
for (self.load_commands.items) |lc| {
|
||||
sizeofcmds += lc.cmdsize();
|
||||
}
|
||||
|
||||
var buffer = try allocator.alloc(u8, sizeofcmds);
|
||||
defer allocator.free(buffer);
|
||||
var fib = std.io.fixedBufferStream(buffer);
|
||||
const writer = fib.writer();
|
||||
for (self.load_commands.items) |lc| {
|
||||
try lc.write(writer);
|
||||
}
|
||||
|
||||
const off = @sizeOf(macho.mach_header_64);
|
||||
log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds });
|
||||
try self.file.pwriteAll(buffer, off);
|
||||
self.load_commands_dirty = false;
|
||||
}
|
||||
|
||||
fn writeHeader(self: *DebugSymbols) !void {
|
||||
fn writeHeader(self: *DebugSymbols, ncmds: u32, sizeofcmds: u32) !void {
|
||||
var header: macho.mach_header_64 = .{};
|
||||
header.filetype = macho.MH_DSYM;
|
||||
|
||||
@ -519,12 +422,8 @@ fn writeHeader(self: *DebugSymbols) !void {
|
||||
else => return error.UnsupportedCpuArchitecture,
|
||||
}
|
||||
|
||||
header.ncmds = @intCast(u32, self.load_commands.items.len);
|
||||
header.sizeofcmds = 0;
|
||||
|
||||
for (self.load_commands.items) |cmd| {
|
||||
header.sizeofcmds += cmd.cmdsize();
|
||||
}
|
||||
header.ncmds = ncmds;
|
||||
header.sizeofcmds = sizeofcmds;
|
||||
|
||||
log.debug("writing Mach-O header {}", .{header});
|
||||
|
||||
@ -532,79 +431,46 @@ fn writeHeader(self: *DebugSymbols) !void {
|
||||
}
|
||||
|
||||
pub fn allocatedSize(self: *DebugSymbols, start: u64) u64 {
|
||||
const seg = self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
assert(start >= seg.inner.fileoff);
|
||||
const seg = self.segments.items[self.dwarf_segment_cmd_index.?];
|
||||
assert(start >= seg.fileoff);
|
||||
var min_pos: u64 = std.math.maxInt(u64);
|
||||
for (seg.sections.items) |section| {
|
||||
for (self.sections.items) |section| {
|
||||
if (section.offset <= start) continue;
|
||||
if (section.offset < min_pos) min_pos = section.offset;
|
||||
}
|
||||
return min_pos - start;
|
||||
}
|
||||
|
||||
fn updateVirtualMemoryMapping(self: *DebugSymbols) !void {
|
||||
const macho_file = self.base;
|
||||
const allocator = macho_file.base.allocator;
|
||||
|
||||
const IndexTuple = std.meta.Tuple(&[_]type{ *?u16, *?u16 });
|
||||
const indices = &[_]IndexTuple{
|
||||
.{ &macho_file.text_segment_cmd_index, &self.text_segment_cmd_index },
|
||||
.{ &macho_file.data_const_segment_cmd_index, &self.data_const_segment_cmd_index },
|
||||
.{ &macho_file.data_segment_cmd_index, &self.data_segment_cmd_index },
|
||||
};
|
||||
|
||||
for (indices) |tuple| {
|
||||
const orig_cmd = macho_file.load_commands.items[tuple[0].*.?].segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, orig_cmd);
|
||||
const comp_cmd = &self.load_commands.items[tuple[1].*.?];
|
||||
comp_cmd.deinit(allocator);
|
||||
self.load_commands.items[tuple[1].*.?] = .{ .segment = cmd };
|
||||
}
|
||||
|
||||
// TODO should we set the linkedit vmsize to that of the binary?
|
||||
const orig_cmd = macho_file.load_commands.items[macho_file.linkedit_segment_cmd_index.?].segment;
|
||||
const orig_vmaddr = orig_cmd.inner.vmaddr;
|
||||
const linkedit_cmd = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
|
||||
linkedit_cmd.inner.vmaddr = orig_vmaddr;
|
||||
|
||||
// Update VM address for the DWARF segment and sections including re-running relocations.
|
||||
// TODO re-run relocations
|
||||
const dwarf_cmd = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
const new_start_aligned = orig_vmaddr + linkedit_cmd.inner.vmsize;
|
||||
const old_start_aligned = dwarf_cmd.inner.vmaddr;
|
||||
const diff = new_start_aligned - old_start_aligned;
|
||||
if (diff > 0) {
|
||||
dwarf_cmd.inner.vmaddr = new_start_aligned;
|
||||
|
||||
for (dwarf_cmd.sections.items) |*sect| {
|
||||
sect.addr += (new_start_aligned - old_start_aligned);
|
||||
}
|
||||
}
|
||||
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
fn writeLinkeditSegment(self: *DebugSymbols) !void {
|
||||
fn writeLinkeditSegmentData(self: *DebugSymbols, ncmds: *u32, lc_writer: anytype) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
try self.writeSymbolTable();
|
||||
try self.writeStringTable();
|
||||
const source_vmaddr = self.base.segments.items[self.base.linkedit_segment_cmd_index.?].vmaddr;
|
||||
const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
|
||||
seg.vmaddr = source_vmaddr;
|
||||
|
||||
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
|
||||
const aligned_size = mem.alignForwardGeneric(u64, seg.inner.filesize, self.base.page_size);
|
||||
seg.inner.filesize = aligned_size;
|
||||
seg.inner.vmsize = aligned_size;
|
||||
var symtab_cmd = macho.symtab_command{
|
||||
.cmdsize = @sizeOf(macho.symtab_command),
|
||||
.symoff = 0,
|
||||
.nsyms = 0,
|
||||
.stroff = 0,
|
||||
.strsize = 0,
|
||||
};
|
||||
try self.writeSymtab(&symtab_cmd);
|
||||
try self.writeStrtab(&symtab_cmd);
|
||||
try lc_writer.writeStruct(symtab_cmd);
|
||||
ncmds.* += 1;
|
||||
|
||||
const aligned_size = mem.alignForwardGeneric(u64, seg.filesize, self.base.page_size);
|
||||
seg.filesize = aligned_size;
|
||||
seg.vmsize = aligned_size;
|
||||
}
|
||||
|
||||
fn writeSymbolTable(self: *DebugSymbols) !void {
|
||||
fn writeSymtab(self: *DebugSymbols, lc: *macho.symtab_command) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const gpa = self.base.base.allocator;
|
||||
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
|
||||
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab;
|
||||
symtab.symoff = @intCast(u32, seg.inner.fileoff);
|
||||
|
||||
var locals = std.ArrayList(macho.nlist_64).init(gpa);
|
||||
defer locals.deinit();
|
||||
@ -634,34 +500,32 @@ fn writeSymbolTable(self: *DebugSymbols) !void {
|
||||
|
||||
const nlocals = locals.items.len;
|
||||
const nexports = exports.items.len;
|
||||
const locals_off = symtab.symoff;
|
||||
const locals_size = nlocals * @sizeOf(macho.nlist_64);
|
||||
const exports_off = locals_off + locals_size;
|
||||
const exports_size = nexports * @sizeOf(macho.nlist_64);
|
||||
const nsyms = nlocals + nexports;
|
||||
|
||||
symtab.nsyms = @intCast(u32, nlocals + nexports);
|
||||
const needed_size = (nlocals + nexports) * @sizeOf(macho.nlist_64);
|
||||
const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
|
||||
const offset = mem.alignForwardGeneric(u64, seg.fileoff, @alignOf(macho.nlist_64));
|
||||
const needed_size = nsyms * @sizeOf(macho.nlist_64);
|
||||
|
||||
if (needed_size > seg.inner.filesize) {
|
||||
if (needed_size > seg.filesize) {
|
||||
const aligned_size = mem.alignForwardGeneric(u64, needed_size, self.base.page_size);
|
||||
const diff = @intCast(u32, aligned_size - seg.inner.filesize);
|
||||
const dwarf_seg = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
seg.inner.filesize = aligned_size;
|
||||
const diff = @intCast(u32, aligned_size - seg.filesize);
|
||||
const dwarf_seg = &self.segments.items[self.dwarf_segment_cmd_index.?];
|
||||
seg.filesize = aligned_size;
|
||||
|
||||
try MachO.copyRangeAllOverlappingAlloc(
|
||||
self.base.base.allocator,
|
||||
self.file,
|
||||
dwarf_seg.inner.fileoff,
|
||||
dwarf_seg.inner.fileoff + diff,
|
||||
math.cast(usize, dwarf_seg.inner.filesize) orelse return error.Overflow,
|
||||
dwarf_seg.fileoff,
|
||||
dwarf_seg.fileoff + diff,
|
||||
math.cast(usize, dwarf_seg.filesize) orelse return error.Overflow,
|
||||
);
|
||||
|
||||
const old_seg_fileoff = dwarf_seg.inner.fileoff;
|
||||
dwarf_seg.inner.fileoff += diff;
|
||||
const old_seg_fileoff = dwarf_seg.fileoff;
|
||||
dwarf_seg.fileoff += diff;
|
||||
|
||||
log.debug(" (moving __DWARF segment from 0x{x} to 0x{x})", .{ old_seg_fileoff, dwarf_seg.inner.fileoff });
|
||||
log.debug(" (moving __DWARF segment from 0x{x} to 0x{x})", .{ old_seg_fileoff, dwarf_seg.fileoff });
|
||||
|
||||
for (dwarf_seg.sections.items) |*sect| {
|
||||
for (self.sections.items) |*sect| {
|
||||
const old_offset = sect.offset;
|
||||
sect.offset += diff;
|
||||
|
||||
@ -674,47 +538,53 @@ fn writeSymbolTable(self: *DebugSymbols) !void {
|
||||
}
|
||||
}
|
||||
|
||||
lc.symoff = @intCast(u32, offset);
|
||||
lc.nsyms = @intCast(u32, nsyms);
|
||||
|
||||
const locals_off = lc.symoff;
|
||||
const locals_size = nlocals * @sizeOf(macho.nlist_64);
|
||||
const exports_off = locals_off + locals_size;
|
||||
const exports_size = nexports * @sizeOf(macho.nlist_64);
|
||||
|
||||
log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off });
|
||||
try self.file.pwriteAll(mem.sliceAsBytes(locals.items), locals_off);
|
||||
|
||||
log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off });
|
||||
try self.file.pwriteAll(mem.sliceAsBytes(exports.items), exports_off);
|
||||
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
|
||||
fn writeStringTable(self: *DebugSymbols) !void {
|
||||
fn writeStrtab(self: *DebugSymbols, lc: *macho.symtab_command) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
|
||||
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab;
|
||||
const symtab_size = @intCast(u32, symtab.nsyms * @sizeOf(macho.nlist_64));
|
||||
symtab.stroff = symtab.symoff + symtab_size;
|
||||
const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
|
||||
const symtab_size = @intCast(u32, lc.nsyms * @sizeOf(macho.nlist_64));
|
||||
const offset = mem.alignForwardGeneric(u64, lc.symoff + symtab_size, @alignOf(u64));
|
||||
lc.stroff = @intCast(u32, offset);
|
||||
|
||||
const needed_size = mem.alignForwardGeneric(u64, self.strtab.buffer.items.len, @alignOf(u64));
|
||||
symtab.strsize = @intCast(u32, needed_size);
|
||||
lc.strsize = @intCast(u32, needed_size);
|
||||
|
||||
if (symtab_size + needed_size > seg.inner.filesize) {
|
||||
const aligned_size = mem.alignForwardGeneric(u64, symtab_size + needed_size, self.base.page_size);
|
||||
const diff = @intCast(u32, aligned_size - seg.inner.filesize);
|
||||
const dwarf_seg = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
|
||||
seg.inner.filesize = aligned_size;
|
||||
if (symtab_size + needed_size > seg.filesize) {
|
||||
const aligned_size = mem.alignForwardGeneric(u64, offset + needed_size, self.base.page_size);
|
||||
const diff = @intCast(u32, aligned_size - seg.filesize);
|
||||
const dwarf_seg = &self.segments.items[self.dwarf_segment_cmd_index.?];
|
||||
seg.filesize = aligned_size;
|
||||
|
||||
try MachO.copyRangeAllOverlappingAlloc(
|
||||
self.base.base.allocator,
|
||||
self.file,
|
||||
dwarf_seg.inner.fileoff,
|
||||
dwarf_seg.inner.fileoff + diff,
|
||||
math.cast(usize, dwarf_seg.inner.filesize) orelse return error.Overflow,
|
||||
dwarf_seg.fileoff,
|
||||
dwarf_seg.fileoff + diff,
|
||||
math.cast(usize, dwarf_seg.filesize) orelse return error.Overflow,
|
||||
);
|
||||
|
||||
const old_seg_fileoff = dwarf_seg.inner.fileoff;
|
||||
dwarf_seg.inner.fileoff += diff;
|
||||
const old_seg_fileoff = dwarf_seg.fileoff;
|
||||
dwarf_seg.fileoff += diff;
|
||||
|
||||
log.debug(" (moving __DWARF segment from 0x{x} to 0x{x})", .{ old_seg_fileoff, dwarf_seg.inner.fileoff });
|
||||
log.debug(" (moving __DWARF segment from 0x{x} to 0x{x})", .{ old_seg_fileoff, dwarf_seg.fileoff });
|
||||
|
||||
for (dwarf_seg.sections.items) |*sect| {
|
||||
for (self.sections.items) |*sect| {
|
||||
const old_offset = sect.offset;
|
||||
sect.offset += diff;
|
||||
|
||||
@ -727,9 +597,7 @@ fn writeStringTable(self: *DebugSymbols) !void {
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
|
||||
log.debug("writing string table from 0x{x} to 0x{x}", .{ lc.stroff, lc.stroff + lc.strsize });
|
||||
|
||||
try self.file.pwriteAll(self.strtab.buffer.items, symtab.stroff);
|
||||
|
||||
self.load_commands_dirty = true;
|
||||
try self.file.pwriteAll(self.strtab.buffer.items, lc.stroff);
|
||||
}
|
||||
|
||||
@ -13,23 +13,9 @@ const fat = @import("fat.zig");
|
||||
const Allocator = mem.Allocator;
|
||||
const CrossTarget = std.zig.CrossTarget;
|
||||
const LibStub = @import("../tapi.zig").LibStub;
|
||||
const LoadCommandIterator = macho.LoadCommandIterator;
|
||||
const MachO = @import("../MachO.zig");
|
||||
|
||||
file: fs.File,
|
||||
name: []const u8,
|
||||
|
||||
header: ?macho.mach_header_64 = null,
|
||||
|
||||
// The actual dylib contents we care about linking with will be embedded at
|
||||
// an offset within a file if we are linking against a fat lib
|
||||
library_offset: u64 = 0,
|
||||
|
||||
load_commands: std.ArrayListUnmanaged(macho.LoadCommand) = .{},
|
||||
|
||||
symtab_cmd_index: ?u16 = null,
|
||||
dysymtab_cmd_index: ?u16 = null,
|
||||
id_cmd_index: ?u16 = null,
|
||||
|
||||
id: ?Id = null,
|
||||
weak: bool = false,
|
||||
|
||||
@ -53,16 +39,12 @@ pub const Id = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fromLoadCommand(allocator: Allocator, lc: macho.GenericCommandWithData(macho.dylib_command)) !Id {
|
||||
const dylib = lc.inner.dylib;
|
||||
const dylib_name = @ptrCast([*:0]const u8, lc.data[dylib.name - @sizeOf(macho.dylib_command) ..]);
|
||||
const name = try allocator.dupe(u8, mem.sliceTo(dylib_name, 0));
|
||||
|
||||
pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id {
|
||||
return Id{
|
||||
.name = name,
|
||||
.timestamp = dylib.timestamp,
|
||||
.current_version = dylib.current_version,
|
||||
.compatibility_version = dylib.compatibility_version,
|
||||
.name = try allocator.dupe(u8, name),
|
||||
.timestamp = lc.dylib.timestamp,
|
||||
.current_version = lc.dylib.current_version,
|
||||
.compatibility_version = lc.dylib.compatibility_version,
|
||||
};
|
||||
}
|
||||
|
||||
@ -126,125 +108,89 @@ pub const Id = struct {
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Dylib, allocator: Allocator) void {
|
||||
for (self.load_commands.items) |*lc| {
|
||||
lc.deinit(allocator);
|
||||
}
|
||||
self.load_commands.deinit(allocator);
|
||||
|
||||
for (self.symbols.keys()) |key| {
|
||||
allocator.free(key);
|
||||
}
|
||||
self.symbols.deinit(allocator);
|
||||
|
||||
allocator.free(self.name);
|
||||
|
||||
if (self.id) |*id| {
|
||||
id.deinit(allocator);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
pub fn parseFromBinary(
|
||||
self: *Dylib,
|
||||
allocator: Allocator,
|
||||
cpu_arch: std.Target.Cpu.Arch,
|
||||
dylib_id: u16,
|
||||
dependent_libs: anytype,
|
||||
name: []const u8,
|
||||
data: []align(@alignOf(u64)) const u8,
|
||||
) !void {
|
||||
log.debug("parsing shared library '{s}'", .{self.name});
|
||||
var stream = std.io.fixedBufferStream(data);
|
||||
const reader = stream.reader();
|
||||
|
||||
self.library_offset = try fat.getLibraryOffset(self.file.reader(), cpu_arch);
|
||||
log.debug("parsing shared library '{s}'", .{name});
|
||||
|
||||
try self.file.seekTo(self.library_offset);
|
||||
const header = try reader.readStruct(macho.mach_header_64);
|
||||
|
||||
var reader = self.file.reader();
|
||||
self.header = try reader.readStruct(macho.mach_header_64);
|
||||
|
||||
if (self.header.?.filetype != macho.MH_DYLIB) {
|
||||
log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, self.header.?.filetype });
|
||||
if (header.filetype != macho.MH_DYLIB) {
|
||||
log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, header.filetype });
|
||||
return error.NotDylib;
|
||||
}
|
||||
|
||||
const this_arch: std.Target.Cpu.Arch = try fat.decodeArch(self.header.?.cputype, true);
|
||||
const this_arch: std.Target.Cpu.Arch = try fat.decodeArch(header.cputype, true);
|
||||
|
||||
if (this_arch != cpu_arch) {
|
||||
log.err("mismatched cpu architecture: expected {}, found {}", .{ cpu_arch, this_arch });
|
||||
log.err("mismatched cpu architecture: expected {s}, found {s}", .{
|
||||
@tagName(cpu_arch),
|
||||
@tagName(this_arch),
|
||||
});
|
||||
return error.MismatchedCpuArchitecture;
|
||||
}
|
||||
|
||||
try self.readLoadCommands(allocator, reader, dylib_id, dependent_libs);
|
||||
try self.parseId(allocator);
|
||||
try self.parseSymbols(allocator);
|
||||
}
|
||||
|
||||
fn readLoadCommands(
|
||||
self: *Dylib,
|
||||
allocator: Allocator,
|
||||
reader: anytype,
|
||||
dylib_id: u16,
|
||||
dependent_libs: anytype,
|
||||
) !void {
|
||||
const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
|
||||
|
||||
try self.load_commands.ensureUnusedCapacity(allocator, self.header.?.ncmds);
|
||||
|
||||
var i: u16 = 0;
|
||||
while (i < self.header.?.ncmds) : (i += 1) {
|
||||
var cmd = try macho.LoadCommand.read(allocator, reader);
|
||||
const should_lookup_reexports = header.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
|
||||
var it = LoadCommandIterator{
|
||||
.ncmds = header.ncmds,
|
||||
.buffer = data[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
|
||||
};
|
||||
while (it.next()) |cmd| {
|
||||
switch (cmd.cmd()) {
|
||||
.SYMTAB => {
|
||||
self.symtab_cmd_index = i;
|
||||
},
|
||||
.DYSYMTAB => {
|
||||
self.dysymtab_cmd_index = i;
|
||||
const symtab_cmd = cmd.cast(macho.symtab_command).?;
|
||||
const symtab = @ptrCast(
|
||||
[*]const macho.nlist_64,
|
||||
@alignCast(@alignOf(macho.nlist_64), &data[symtab_cmd.symoff]),
|
||||
)[0..symtab_cmd.nsyms];
|
||||
const strtab = data[symtab_cmd.stroff..][0..symtab_cmd.strsize];
|
||||
|
||||
for (symtab) |sym| {
|
||||
const add_to_symtab = sym.ext() and (sym.sect() or sym.indr());
|
||||
if (!add_to_symtab) continue;
|
||||
|
||||
const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0);
|
||||
try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
|
||||
}
|
||||
},
|
||||
.ID_DYLIB => {
|
||||
self.id_cmd_index = i;
|
||||
self.id = try Id.fromLoadCommand(
|
||||
allocator,
|
||||
cmd.cast(macho.dylib_command).?,
|
||||
cmd.getDylibPathName(),
|
||||
);
|
||||
},
|
||||
.REEXPORT_DYLIB => {
|
||||
if (should_lookup_reexports) {
|
||||
// Parse install_name to dependent dylib.
|
||||
var id = try Id.fromLoadCommand(allocator, cmd.dylib);
|
||||
var id = try Id.fromLoadCommand(
|
||||
allocator,
|
||||
cmd.cast(macho.dylib_command).?,
|
||||
cmd.getDylibPathName(),
|
||||
);
|
||||
try dependent_libs.writeItem(.{ .id = id, .parent = dylib_id });
|
||||
}
|
||||
},
|
||||
else => {
|
||||
log.debug("Unknown load command detected: 0x{x}.", .{@enumToInt(cmd.cmd())});
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
self.load_commands.appendAssumeCapacity(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
fn parseId(self: *Dylib, allocator: Allocator) !void {
|
||||
const index = self.id_cmd_index orelse {
|
||||
log.debug("no LC_ID_DYLIB load command found; using hard-coded defaults...", .{});
|
||||
self.id = try Id.default(allocator, self.name);
|
||||
return;
|
||||
};
|
||||
self.id = try Id.fromLoadCommand(allocator, self.load_commands.items[index].dylib);
|
||||
}
|
||||
|
||||
fn parseSymbols(self: *Dylib, allocator: Allocator) !void {
|
||||
const index = self.symtab_cmd_index orelse return;
|
||||
const symtab_cmd = self.load_commands.items[index].symtab;
|
||||
|
||||
const symtab = try allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms);
|
||||
defer allocator.free(symtab);
|
||||
_ = try self.file.preadAll(symtab, symtab_cmd.symoff + self.library_offset);
|
||||
const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, symtab));
|
||||
|
||||
const strtab = try allocator.alloc(u8, symtab_cmd.strsize);
|
||||
defer allocator.free(strtab);
|
||||
_ = try self.file.preadAll(strtab, symtab_cmd.stroff + self.library_offset);
|
||||
|
||||
for (slice) |sym| {
|
||||
const add_to_symtab = sym.ext() and (sym.sect() or sym.indr());
|
||||
|
||||
if (!add_to_symtab) continue;
|
||||
|
||||
const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0);
|
||||
const name = try allocator.dupe(u8, sym_name);
|
||||
try self.symbols.putNoClobber(allocator, name, {});
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,10 +302,11 @@ pub fn parseFromStub(
|
||||
lib_stub: LibStub,
|
||||
dylib_id: u16,
|
||||
dependent_libs: anytype,
|
||||
name: []const u8,
|
||||
) !void {
|
||||
if (lib_stub.inner.len == 0) return error.EmptyStubFile;
|
||||
|
||||
log.debug("parsing shared library from stub '{s}'", .{self.name});
|
||||
log.debug("parsing shared library from stub '{s}'", .{name});
|
||||
|
||||
const umbrella_lib = lib_stub.inner[0];
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ const Object = @This();
|
||||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
const assert = std.debug.assert;
|
||||
const dwarf = std.dwarf;
|
||||
const fs = std.fs;
|
||||
const io = std.io;
|
||||
const log = std.log.scoped(.link);
|
||||
@ -14,43 +15,20 @@ const trace = @import("../../tracy.zig").trace;
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
const Atom = @import("Atom.zig");
|
||||
const LoadCommandIterator = macho.LoadCommandIterator;
|
||||
const MachO = @import("../MachO.zig");
|
||||
const MatchingSection = MachO.MatchingSection;
|
||||
const SymbolWithLoc = MachO.SymbolWithLoc;
|
||||
|
||||
file: fs.File,
|
||||
name: []const u8,
|
||||
mtime: u64,
|
||||
|
||||
/// Data contents of the file. Includes sections, and data of load commands.
|
||||
/// Excludes the backing memory for the header and load commands.
|
||||
/// Initialized in `parse`.
|
||||
contents: []const u8 = undefined,
|
||||
|
||||
file_offset: ?u32 = null,
|
||||
contents: []align(@alignOf(u64)) const u8,
|
||||
|
||||
header: macho.mach_header_64 = undefined,
|
||||
|
||||
load_commands: std.ArrayListUnmanaged(macho.LoadCommand) = .{},
|
||||
|
||||
segment_cmd_index: ?u16 = null,
|
||||
text_section_index: ?u16 = null,
|
||||
symtab_cmd_index: ?u16 = null,
|
||||
dysymtab_cmd_index: ?u16 = null,
|
||||
build_version_cmd_index: ?u16 = null,
|
||||
data_in_code_cmd_index: ?u16 = null,
|
||||
|
||||
// __DWARF segment sections
|
||||
dwarf_debug_info_index: ?u16 = null,
|
||||
dwarf_debug_abbrev_index: ?u16 = null,
|
||||
dwarf_debug_str_index: ?u16 = null,
|
||||
dwarf_debug_line_index: ?u16 = null,
|
||||
dwarf_debug_line_str_index: ?u16 = null,
|
||||
dwarf_debug_ranges_index: ?u16 = null,
|
||||
in_symtab: []const macho.nlist_64 = undefined,
|
||||
in_strtab: []const u8 = undefined,
|
||||
|
||||
symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
|
||||
strtab: []const u8 = &.{},
|
||||
data_in_code_entries: []const macho.data_in_code_entry = &.{},
|
||||
sections: std.ArrayListUnmanaged(macho.section_64) = .{},
|
||||
|
||||
sections_as_symbols: std.AutoHashMapUnmanaged(u16, u32) = .{},
|
||||
|
||||
@ -61,12 +39,8 @@ managed_atoms: std.ArrayListUnmanaged(*Atom) = .{},
|
||||
atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{},
|
||||
|
||||
pub fn deinit(self: *Object, gpa: Allocator) void {
|
||||
for (self.load_commands.items) |*lc| {
|
||||
lc.deinit(gpa);
|
||||
}
|
||||
self.load_commands.deinit(gpa);
|
||||
gpa.free(self.contents);
|
||||
self.symtab.deinit(gpa);
|
||||
self.sections.deinit(gpa);
|
||||
self.sections_as_symbols.deinit(gpa);
|
||||
self.atom_by_index_table.deinit(gpa);
|
||||
|
||||
@ -77,22 +51,15 @@ pub fn deinit(self: *Object, gpa: Allocator) void {
|
||||
self.managed_atoms.deinit(gpa);
|
||||
|
||||
gpa.free(self.name);
|
||||
gpa.free(self.contents);
|
||||
}
|
||||
|
||||
pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch) !void {
|
||||
const file_stat = try self.file.stat();
|
||||
const file_size = math.cast(usize, file_stat.size) orelse return error.Overflow;
|
||||
self.contents = try self.file.readToEndAlloc(allocator, file_size);
|
||||
|
||||
var stream = std.io.fixedBufferStream(self.contents);
|
||||
const reader = stream.reader();
|
||||
|
||||
const file_offset = self.file_offset orelse 0;
|
||||
if (file_offset > 0) {
|
||||
try reader.context.seekTo(file_offset);
|
||||
}
|
||||
|
||||
self.header = try reader.readStruct(macho.mach_header_64);
|
||||
|
||||
if (self.header.filetype != macho.MH_OBJECT) {
|
||||
log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{
|
||||
macho.MH_OBJECT,
|
||||
@ -110,92 +77,54 @@ pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch)
|
||||
},
|
||||
};
|
||||
if (this_arch != cpu_arch) {
|
||||
log.err("mismatched cpu architecture: expected {}, found {}", .{ cpu_arch, this_arch });
|
||||
log.err("mismatched cpu architecture: expected {s}, found {s}", .{
|
||||
@tagName(cpu_arch),
|
||||
@tagName(this_arch),
|
||||
});
|
||||
return error.MismatchedCpuArchitecture;
|
||||
}
|
||||
|
||||
try self.load_commands.ensureUnusedCapacity(allocator, self.header.ncmds);
|
||||
|
||||
var i: u16 = 0;
|
||||
while (i < self.header.ncmds) : (i += 1) {
|
||||
var cmd = try macho.LoadCommand.read(allocator, reader);
|
||||
var it = LoadCommandIterator{
|
||||
.ncmds = self.header.ncmds,
|
||||
.buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
|
||||
};
|
||||
while (it.next()) |cmd| {
|
||||
switch (cmd.cmd()) {
|
||||
.SEGMENT_64 => {
|
||||
self.segment_cmd_index = i;
|
||||
var seg = cmd.segment;
|
||||
for (seg.sections.items) |*sect, j| {
|
||||
const index = @intCast(u16, j);
|
||||
const segname = sect.segName();
|
||||
const sectname = sect.sectName();
|
||||
if (mem.eql(u8, segname, "__DWARF")) {
|
||||
if (mem.eql(u8, sectname, "__debug_info")) {
|
||||
self.dwarf_debug_info_index = index;
|
||||
} else if (mem.eql(u8, sectname, "__debug_abbrev")) {
|
||||
self.dwarf_debug_abbrev_index = index;
|
||||
} else if (mem.eql(u8, sectname, "__debug_str")) {
|
||||
self.dwarf_debug_str_index = index;
|
||||
} else if (mem.eql(u8, sectname, "__debug_line")) {
|
||||
self.dwarf_debug_line_index = index;
|
||||
} else if (mem.eql(u8, sectname, "__debug_line_str")) {
|
||||
self.dwarf_debug_line_str_index = index;
|
||||
} else if (mem.eql(u8, sectname, "__debug_ranges")) {
|
||||
self.dwarf_debug_ranges_index = index;
|
||||
}
|
||||
} else if (mem.eql(u8, segname, "__TEXT")) {
|
||||
if (mem.eql(u8, sectname, "__text")) {
|
||||
self.text_section_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
sect.offset += file_offset;
|
||||
if (sect.reloff > 0) {
|
||||
sect.reloff += file_offset;
|
||||
}
|
||||
const segment = cmd.cast(macho.segment_command_64).?;
|
||||
try self.sections.ensureUnusedCapacity(allocator, segment.nsects);
|
||||
for (cmd.getSections()) |sect| {
|
||||
self.sections.appendAssumeCapacity(sect);
|
||||
}
|
||||
|
||||
seg.inner.fileoff += file_offset;
|
||||
},
|
||||
.SYMTAB => {
|
||||
self.symtab_cmd_index = i;
|
||||
cmd.symtab.symoff += file_offset;
|
||||
cmd.symtab.stroff += file_offset;
|
||||
},
|
||||
.DYSYMTAB => {
|
||||
self.dysymtab_cmd_index = i;
|
||||
},
|
||||
.BUILD_VERSION => {
|
||||
self.build_version_cmd_index = i;
|
||||
},
|
||||
.DATA_IN_CODE => {
|
||||
self.data_in_code_cmd_index = i;
|
||||
cmd.linkedit_data.dataoff += file_offset;
|
||||
},
|
||||
else => {
|
||||
log.debug("Unknown load command detected: 0x{x}.", .{@enumToInt(cmd.cmd())});
|
||||
const symtab = cmd.cast(macho.symtab_command).?;
|
||||
self.in_symtab = @ptrCast(
|
||||
[*]const macho.nlist_64,
|
||||
@alignCast(@alignOf(macho.nlist_64), &self.contents[symtab.symoff]),
|
||||
)[0..symtab.nsyms];
|
||||
self.in_strtab = self.contents[symtab.stroff..][0..symtab.strsize];
|
||||
try self.symtab.appendSlice(allocator, self.in_symtab);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
self.load_commands.appendAssumeCapacity(cmd);
|
||||
}
|
||||
|
||||
try self.parseSymtab(allocator);
|
||||
}
|
||||
|
||||
const Context = struct {
|
||||
symtab: []const macho.nlist_64,
|
||||
strtab: []const u8,
|
||||
object: *const Object,
|
||||
};
|
||||
|
||||
const SymbolAtIndex = struct {
|
||||
index: u32,
|
||||
|
||||
fn getSymbol(self: SymbolAtIndex, ctx: Context) macho.nlist_64 {
|
||||
return ctx.symtab[self.index];
|
||||
return ctx.object.getSourceSymbol(self.index).?;
|
||||
}
|
||||
|
||||
fn getSymbolName(self: SymbolAtIndex, ctx: Context) []const u8 {
|
||||
const sym = self.getSymbol(ctx);
|
||||
assert(sym.n_strx < ctx.strtab.len);
|
||||
return mem.sliceTo(@ptrCast([*:0]const u8, ctx.strtab.ptr + sym.n_strx), 0);
|
||||
return ctx.object.getString(sym.n_strx);
|
||||
}
|
||||
|
||||
/// Returns whether lhs is less than rhs by allocated address in object file.
|
||||
@ -285,6 +214,23 @@ fn filterRelocs(
|
||||
return relocs[start..end];
|
||||
}
|
||||
|
||||
pub fn scanInputSections(self: Object, macho_file: *MachO) !void {
|
||||
for (self.sections.items) |sect| {
|
||||
const match = (try macho_file.getOutputSection(sect)) orelse {
|
||||
log.debug(" unhandled section", .{});
|
||||
continue;
|
||||
};
|
||||
const output = macho_file.sections.items(.header)[match];
|
||||
log.debug("mapping '{s},{s}' into output sect({d}, '{s},{s}')", .{
|
||||
sect.segName(),
|
||||
sect.sectName(),
|
||||
match + 1,
|
||||
output.segName(),
|
||||
output.sectName(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits object into atoms assuming one-shot linking mode.
|
||||
pub fn splitIntoAtomsOneShot(self: *Object, macho_file: *MachO, object_id: u32) !void {
|
||||
assert(macho_file.mode == .one_shot);
|
||||
@ -293,7 +239,6 @@ pub fn splitIntoAtomsOneShot(self: *Object, macho_file: *MachO, object_id: u32)
|
||||
defer tracy.end();
|
||||
|
||||
const gpa = macho_file.base.allocator;
|
||||
const seg = self.load_commands.items[self.segment_cmd_index.?].segment;
|
||||
|
||||
log.debug("splitting object({d}, {s}) into atoms: one-shot mode", .{ object_id, self.name });
|
||||
|
||||
@ -302,13 +247,12 @@ pub fn splitIntoAtomsOneShot(self: *Object, macho_file: *MachO, object_id: u32)
|
||||
// the GO compiler does not necessarily respect that therefore we sort immediately by type
|
||||
// and address within.
|
||||
const context = Context{
|
||||
.symtab = self.getSourceSymtab(),
|
||||
.strtab = self.strtab,
|
||||
.object = self,
|
||||
};
|
||||
var sorted_all_syms = try std.ArrayList(SymbolAtIndex).initCapacity(gpa, context.symtab.len);
|
||||
var sorted_all_syms = try std.ArrayList(SymbolAtIndex).initCapacity(gpa, self.in_symtab.len);
|
||||
defer sorted_all_syms.deinit();
|
||||
|
||||
for (context.symtab) |_, index| {
|
||||
for (self.in_symtab) |_, index| {
|
||||
sorted_all_syms.appendAssumeCapacity(.{ .index = @intCast(u32, index) });
|
||||
}
|
||||
|
||||
@ -320,53 +264,48 @@ pub fn splitIntoAtomsOneShot(self: *Object, macho_file: *MachO, object_id: u32)
|
||||
|
||||
// Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we
|
||||
// have to infer the start of undef section in the symtab ourselves.
|
||||
const iundefsym = if (self.dysymtab_cmd_index) |cmd_index| blk: {
|
||||
const dysymtab = self.load_commands.items[cmd_index].dysymtab;
|
||||
const iundefsym = blk: {
|
||||
const dysymtab = self.parseDysymtab() orelse {
|
||||
var iundefsym: usize = sorted_all_syms.items.len;
|
||||
while (iundefsym > 0) : (iundefsym -= 1) {
|
||||
const sym = sorted_all_syms.items[iundefsym - 1].getSymbol(context);
|
||||
if (sym.sect()) break;
|
||||
}
|
||||
break :blk iundefsym;
|
||||
};
|
||||
break :blk dysymtab.iundefsym;
|
||||
} else blk: {
|
||||
var iundefsym: usize = sorted_all_syms.items.len;
|
||||
while (iundefsym > 0) : (iundefsym -= 1) {
|
||||
const sym = sorted_all_syms.items[iundefsym - 1].getSymbol(context);
|
||||
if (sym.sect()) break;
|
||||
}
|
||||
break :blk iundefsym;
|
||||
};
|
||||
|
||||
// We only care about defined symbols, so filter every other out.
|
||||
const sorted_syms = sorted_all_syms.items[0..iundefsym];
|
||||
const subsections_via_symbols = self.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
|
||||
|
||||
for (seg.sections.items) |sect, id| {
|
||||
for (self.sections.items) |sect, id| {
|
||||
const sect_id = @intCast(u8, id);
|
||||
log.debug("splitting section '{s},{s}' into atoms", .{ sect.segName(), sect.sectName() });
|
||||
|
||||
// Get matching segment/section in the final artifact.
|
||||
const match = (try macho_file.getMatchingSection(sect)) orelse {
|
||||
const match = (try macho_file.getOutputSection(sect)) orelse {
|
||||
log.debug(" unhandled section", .{});
|
||||
continue;
|
||||
};
|
||||
|
||||
log.debug(" output sect({d}, '{s},{s}')", .{
|
||||
macho_file.getSectionOrdinal(match),
|
||||
macho_file.getSection(match).segName(),
|
||||
macho_file.getSection(match).sectName(),
|
||||
match + 1,
|
||||
macho_file.sections.items(.header)[match].segName(),
|
||||
macho_file.sections.items(.header)[match].sectName(),
|
||||
});
|
||||
|
||||
const cpu_arch = macho_file.base.options.target.cpu.arch;
|
||||
const is_zerofill = blk: {
|
||||
const section_type = sect.type_();
|
||||
break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL;
|
||||
};
|
||||
|
||||
// Read section's code
|
||||
const code: ?[]const u8 = if (!is_zerofill) try self.getSectionContents(sect_id) else null;
|
||||
const code: ?[]const u8 = if (!sect.isZerofill()) try self.getSectionContents(sect) else null;
|
||||
|
||||
// Read section's list of relocations
|
||||
const raw_relocs = self.contents[sect.reloff..][0 .. sect.nreloc * @sizeOf(macho.relocation_info)];
|
||||
const relocs = mem.bytesAsSlice(
|
||||
macho.relocation_info,
|
||||
@alignCast(@alignOf(macho.relocation_info), raw_relocs),
|
||||
);
|
||||
const relocs = @ptrCast(
|
||||
[*]const macho.relocation_info,
|
||||
@alignCast(@alignOf(macho.relocation_info), &self.contents[sect.reloff]),
|
||||
)[0..sect.nreloc];
|
||||
|
||||
// Symbols within this section only.
|
||||
const filtered_syms = filterSymbolsByAddress(
|
||||
@ -387,7 +326,7 @@ pub fn splitIntoAtomsOneShot(self: *Object, macho_file: *MachO, object_id: u32)
|
||||
try self.symtab.append(gpa, .{
|
||||
.n_strx = 0,
|
||||
.n_type = macho.N_SECT,
|
||||
.n_sect = macho_file.getSectionOrdinal(match),
|
||||
.n_sect = match + 1,
|
||||
.n_desc = 0,
|
||||
.n_value = sect.addr,
|
||||
});
|
||||
@ -476,7 +415,7 @@ pub fn splitIntoAtomsOneShot(self: *Object, macho_file: *MachO, object_id: u32)
|
||||
try self.symtab.append(gpa, .{
|
||||
.n_strx = 0,
|
||||
.n_type = macho.N_SECT,
|
||||
.n_sect = macho_file.getSectionOrdinal(match),
|
||||
.n_sect = match + 1,
|
||||
.n_desc = 0,
|
||||
.n_value = addr,
|
||||
});
|
||||
@ -501,7 +440,7 @@ pub fn splitIntoAtomsOneShot(self: *Object, macho_file: *MachO, object_id: u32)
|
||||
try self.symtab.append(gpa, .{
|
||||
.n_strx = 0,
|
||||
.n_type = macho.N_SECT,
|
||||
.n_sect = macho_file.getSectionOrdinal(match),
|
||||
.n_sect = match + 1,
|
||||
.n_desc = 0,
|
||||
.n_value = sect.addr,
|
||||
});
|
||||
@ -535,21 +474,21 @@ fn createAtomFromSubsection(
|
||||
code: ?[]const u8,
|
||||
relocs: []const macho.relocation_info,
|
||||
indexes: []const SymbolAtIndex,
|
||||
match: MatchingSection,
|
||||
match: u8,
|
||||
sect: macho.section_64,
|
||||
) !*Atom {
|
||||
const gpa = macho_file.base.allocator;
|
||||
const sym = self.symtab.items[sym_index];
|
||||
const atom = try MachO.createEmptyAtom(gpa, sym_index, size, alignment);
|
||||
atom.file = object_id;
|
||||
self.symtab.items[sym_index].n_sect = macho_file.getSectionOrdinal(match);
|
||||
self.symtab.items[sym_index].n_sect = match + 1;
|
||||
|
||||
log.debug("creating ATOM(%{d}, '{s}') in sect({d}, '{s},{s}') in object({d})", .{
|
||||
sym_index,
|
||||
self.getString(sym.n_strx),
|
||||
macho_file.getSectionOrdinal(match),
|
||||
macho_file.getSection(match).segName(),
|
||||
macho_file.getSection(match).sectName(),
|
||||
match + 1,
|
||||
macho_file.sections.items(.header)[match].segName(),
|
||||
macho_file.sections.items(.header)[match].sectName(),
|
||||
object_id,
|
||||
});
|
||||
|
||||
@ -577,7 +516,7 @@ fn createAtomFromSubsection(
|
||||
try atom.contained.ensureTotalCapacity(gpa, indexes.len);
|
||||
for (indexes) |inner_sym_index| {
|
||||
const inner_sym = &self.symtab.items[inner_sym_index.index];
|
||||
inner_sym.n_sect = macho_file.getSectionOrdinal(match);
|
||||
inner_sym.n_sect = match + 1;
|
||||
atom.contained.appendAssumeCapacity(.{
|
||||
.sym_index = inner_sym_index.index,
|
||||
.offset = inner_sym.n_value - sym.n_value,
|
||||
@ -589,48 +528,84 @@ fn createAtomFromSubsection(
|
||||
return atom;
|
||||
}
|
||||
|
||||
fn parseSymtab(self: *Object, allocator: Allocator) !void {
|
||||
const index = self.symtab_cmd_index orelse return;
|
||||
const symtab = self.load_commands.items[index].symtab;
|
||||
try self.symtab.appendSlice(allocator, self.getSourceSymtab());
|
||||
self.strtab = self.contents[symtab.stroff..][0..symtab.strsize];
|
||||
}
|
||||
|
||||
pub fn getSourceSymtab(self: Object) []const macho.nlist_64 {
|
||||
const index = self.symtab_cmd_index orelse return &[0]macho.nlist_64{};
|
||||
const symtab = self.load_commands.items[index].symtab;
|
||||
const symtab_size = @sizeOf(macho.nlist_64) * symtab.nsyms;
|
||||
const raw_symtab = self.contents[symtab.symoff..][0..symtab_size];
|
||||
return mem.bytesAsSlice(
|
||||
macho.nlist_64,
|
||||
@alignCast(@alignOf(macho.nlist_64), raw_symtab),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getSourceSymbol(self: Object, index: u32) ?macho.nlist_64 {
|
||||
const symtab = self.getSourceSymtab();
|
||||
if (index >= symtab.len) return null;
|
||||
return symtab[index];
|
||||
if (index >= self.in_symtab.len) return null;
|
||||
return self.in_symtab[index];
|
||||
}
|
||||
|
||||
pub fn getSourceSection(self: Object, index: u16) macho.section_64 {
|
||||
const seg = self.load_commands.items[self.segment_cmd_index.?].segment;
|
||||
assert(index < seg.sections.items.len);
|
||||
return seg.sections.items[index];
|
||||
assert(index < self.sections.items.len);
|
||||
return self.sections.items[index];
|
||||
}
|
||||
|
||||
pub fn parseDataInCode(self: Object) ?[]const macho.data_in_code_entry {
|
||||
const index = self.data_in_code_cmd_index orelse return null;
|
||||
const data_in_code = self.load_commands.items[index].linkedit_data;
|
||||
const raw_dice = self.contents[data_in_code.dataoff..][0..data_in_code.datasize];
|
||||
return mem.bytesAsSlice(
|
||||
macho.data_in_code_entry,
|
||||
@alignCast(@alignOf(macho.data_in_code_entry), raw_dice),
|
||||
);
|
||||
var it = LoadCommandIterator{
|
||||
.ncmds = self.header.ncmds,
|
||||
.buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
|
||||
};
|
||||
while (it.next()) |cmd| {
|
||||
switch (cmd.cmd()) {
|
||||
.DATA_IN_CODE => {
|
||||
const dice = cmd.cast(macho.linkedit_data_command).?;
|
||||
const ndice = @divExact(dice.datasize, @sizeOf(macho.data_in_code_entry));
|
||||
return @ptrCast(
|
||||
[*]const macho.data_in_code_entry,
|
||||
@alignCast(@alignOf(macho.data_in_code_entry), &self.contents[dice.dataoff]),
|
||||
)[0..ndice];
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
} else return null;
|
||||
}
|
||||
|
||||
pub fn getSectionContents(self: Object, index: u16) error{Overflow}![]const u8 {
|
||||
const sect = self.getSourceSection(index);
|
||||
fn parseDysymtab(self: Object) ?macho.dysymtab_command {
|
||||
var it = LoadCommandIterator{
|
||||
.ncmds = self.header.ncmds,
|
||||
.buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
|
||||
};
|
||||
while (it.next()) |cmd| {
|
||||
switch (cmd.cmd()) {
|
||||
.DYSYMTAB => {
|
||||
return cmd.cast(macho.dysymtab_command).?;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
} else return null;
|
||||
}
|
||||
|
||||
pub fn parseDwarfInfo(self: Object) error{Overflow}!dwarf.DwarfInfo {
|
||||
var di = dwarf.DwarfInfo{
|
||||
.endian = .Little,
|
||||
.debug_info = &[0]u8{},
|
||||
.debug_abbrev = &[0]u8{},
|
||||
.debug_str = &[0]u8{},
|
||||
.debug_line = &[0]u8{},
|
||||
.debug_line_str = &[0]u8{},
|
||||
.debug_ranges = &[0]u8{},
|
||||
};
|
||||
for (self.sections.items) |sect| {
|
||||
const segname = sect.segName();
|
||||
const sectname = sect.sectName();
|
||||
if (mem.eql(u8, segname, "__DWARF")) {
|
||||
if (mem.eql(u8, sectname, "__debug_info")) {
|
||||
di.debug_info = try self.getSectionContents(sect);
|
||||
} else if (mem.eql(u8, sectname, "__debug_abbrev")) {
|
||||
di.debug_abbrev = try self.getSectionContents(sect);
|
||||
} else if (mem.eql(u8, sectname, "__debug_str")) {
|
||||
di.debug_str = try self.getSectionContents(sect);
|
||||
} else if (mem.eql(u8, sectname, "__debug_line")) {
|
||||
di.debug_line = try self.getSectionContents(sect);
|
||||
} else if (mem.eql(u8, sectname, "__debug_line_str")) {
|
||||
di.debug_line_str = try self.getSectionContents(sect);
|
||||
} else if (mem.eql(u8, sectname, "__debug_ranges")) {
|
||||
di.debug_ranges = try self.getSectionContents(sect);
|
||||
}
|
||||
}
|
||||
}
|
||||
return di;
|
||||
}
|
||||
|
||||
pub fn getSectionContents(self: Object, sect: macho.section_64) error{Overflow}![]const u8 {
|
||||
const size = math.cast(usize, sect.size) orelse return error.Overflow;
|
||||
log.debug("getting {s},{s} data at 0x{x} - 0x{x}", .{
|
||||
sect.segName(),
|
||||
@ -642,8 +617,8 @@ pub fn getSectionContents(self: Object, index: u16) error{Overflow}![]const u8 {
|
||||
}
|
||||
|
||||
pub fn getString(self: Object, off: u32) []const u8 {
|
||||
assert(off < self.strtab.len);
|
||||
return mem.sliceTo(@ptrCast([*:0]const u8, self.strtab.ptr + off), 0);
|
||||
assert(off < self.in_strtab.len);
|
||||
return mem.sliceTo(@ptrCast([*:0]const u8, self.in_strtab.ptr + off), 0);
|
||||
}
|
||||
|
||||
pub fn getAtomForSymbol(self: Object, sym_index: u32) ?*Atom {
|
||||
|
||||
@ -8,7 +8,6 @@ const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
const Atom = @import("Atom.zig");
|
||||
const MachO = @import("../MachO.zig");
|
||||
const MatchingSection = MachO.MatchingSection;
|
||||
|
||||
pub fn gcAtoms(macho_file: *MachO) !void {
|
||||
const gpa = macho_file.base.allocator;
|
||||
@ -25,12 +24,12 @@ pub fn gcAtoms(macho_file: *MachO) !void {
|
||||
try prune(arena, alive, macho_file);
|
||||
}
|
||||
|
||||
fn removeAtomFromSection(atom: *Atom, match: MatchingSection, macho_file: *MachO) void {
|
||||
const sect = macho_file.getSectionPtr(match);
|
||||
fn removeAtomFromSection(atom: *Atom, match: u8, macho_file: *MachO) void {
|
||||
var section = macho_file.sections.get(match);
|
||||
|
||||
// If we want to enable GC for incremental codepath, we need to take into
|
||||
// account any padding that might have been left here.
|
||||
sect.size -= atom.size;
|
||||
section.header.size -= atom.size;
|
||||
|
||||
if (atom.prev) |prev| {
|
||||
prev.next = atom.next;
|
||||
@ -38,15 +37,16 @@ fn removeAtomFromSection(atom: *Atom, match: MatchingSection, macho_file: *MachO
|
||||
if (atom.next) |next| {
|
||||
next.prev = atom.prev;
|
||||
} else {
|
||||
const last = macho_file.atoms.getPtr(match).?;
|
||||
if (atom.prev) |prev| {
|
||||
last.* = prev;
|
||||
section.last_atom = prev;
|
||||
} else {
|
||||
// The section will be GCed in the next step.
|
||||
last.* = undefined;
|
||||
sect.size = 0;
|
||||
section.last_atom = null;
|
||||
section.header.size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
macho_file.sections.set(match, section);
|
||||
}
|
||||
|
||||
fn collectRoots(roots: *std.AutoHashMap(*Atom, void), macho_file: *MachO) !void {
|
||||
@ -93,7 +93,7 @@ fn collectRoots(roots: *std.AutoHashMap(*Atom, void), macho_file: *MachO) !void
|
||||
const is_gc_root = blk: {
|
||||
if (source_sect.isDontDeadStrip()) break :blk true;
|
||||
if (mem.eql(u8, "__StaticInit", source_sect.sectName())) break :blk true;
|
||||
switch (source_sect.type_()) {
|
||||
switch (source_sect.@"type"()) {
|
||||
macho.S_MOD_INIT_FUNC_POINTERS,
|
||||
macho.S_MOD_TERM_FUNC_POINTERS,
|
||||
=> break :blk true,
|
||||
@ -173,19 +173,19 @@ fn mark(
|
||||
fn prune(arena: Allocator, alive: std.AutoHashMap(*Atom, void), macho_file: *MachO) !void {
|
||||
// Any section that ends up here will be updated, that is,
|
||||
// its size and alignment recalculated.
|
||||
var gc_sections = std.AutoHashMap(MatchingSection, void).init(arena);
|
||||
var gc_sections = std.AutoHashMap(u8, void).init(arena);
|
||||
var loop: bool = true;
|
||||
while (loop) {
|
||||
loop = false;
|
||||
|
||||
for (macho_file.objects.items) |object| {
|
||||
for (object.getSourceSymtab()) |_, source_index| {
|
||||
for (object.in_symtab) |_, source_index| {
|
||||
const atom = object.getAtomForSymbol(@intCast(u32, source_index)) orelse continue;
|
||||
if (alive.contains(atom)) continue;
|
||||
|
||||
const global = atom.getSymbolWithLoc();
|
||||
const sym = atom.getSymbolPtr(macho_file);
|
||||
const match = macho_file.getMatchingSectionFromOrdinal(sym.n_sect);
|
||||
const match = sym.n_sect - 1;
|
||||
|
||||
if (sym.n_desc == MachO.N_DESC_GCED) continue;
|
||||
if (!sym.ext() and !refersDead(atom, macho_file)) continue;
|
||||
@ -232,7 +232,7 @@ fn prune(arena: Allocator, alive: std.AutoHashMap(*Atom, void), macho_file: *Mac
|
||||
|
||||
// TODO tombstone
|
||||
const atom = entry.getAtom(macho_file);
|
||||
const match = macho_file.getMatchingSectionFromOrdinal(sym.n_sect);
|
||||
const match = sym.n_sect - 1;
|
||||
removeAtomFromSection(atom, match, macho_file);
|
||||
_ = try gc_sections.put(match, {});
|
||||
_ = macho_file.got_entries_table.remove(entry.target);
|
||||
@ -244,7 +244,7 @@ fn prune(arena: Allocator, alive: std.AutoHashMap(*Atom, void), macho_file: *Mac
|
||||
|
||||
// TODO tombstone
|
||||
const atom = entry.getAtom(macho_file);
|
||||
const match = macho_file.getMatchingSectionFromOrdinal(sym.n_sect);
|
||||
const match = sym.n_sect - 1;
|
||||
removeAtomFromSection(atom, match, macho_file);
|
||||
_ = try gc_sections.put(match, {});
|
||||
_ = macho_file.stubs_table.remove(entry.target);
|
||||
@ -256,7 +256,7 @@ fn prune(arena: Allocator, alive: std.AutoHashMap(*Atom, void), macho_file: *Mac
|
||||
|
||||
// TODO tombstone
|
||||
const atom = entry.getAtom(macho_file);
|
||||
const match = macho_file.getMatchingSectionFromOrdinal(sym.n_sect);
|
||||
const match = sym.n_sect - 1;
|
||||
removeAtomFromSection(atom, match, macho_file);
|
||||
_ = try gc_sections.put(match, {});
|
||||
_ = macho_file.tlv_ptr_entries_table.remove(entry.target);
|
||||
@ -265,13 +265,13 @@ fn prune(arena: Allocator, alive: std.AutoHashMap(*Atom, void), macho_file: *Mac
|
||||
var gc_sections_it = gc_sections.iterator();
|
||||
while (gc_sections_it.next()) |entry| {
|
||||
const match = entry.key_ptr.*;
|
||||
const sect = macho_file.getSectionPtr(match);
|
||||
if (sect.size == 0) continue; // Pruning happens automatically in next step.
|
||||
var section = macho_file.sections.get(match);
|
||||
if (section.header.size == 0) continue; // Pruning happens automatically in next step.
|
||||
|
||||
sect.@"align" = 0;
|
||||
sect.size = 0;
|
||||
section.header.@"align" = 0;
|
||||
section.header.size = 0;
|
||||
|
||||
var atom = macho_file.atoms.get(match).?;
|
||||
var atom = section.last_atom.?;
|
||||
|
||||
while (atom.prev) |prev| {
|
||||
atom = prev;
|
||||
@ -279,14 +279,16 @@ fn prune(arena: Allocator, alive: std.AutoHashMap(*Atom, void), macho_file: *Mac
|
||||
|
||||
while (true) {
|
||||
const atom_alignment = try math.powi(u32, 2, atom.alignment);
|
||||
const aligned_end_addr = mem.alignForwardGeneric(u64, sect.size, atom_alignment);
|
||||
const padding = aligned_end_addr - sect.size;
|
||||
sect.size += padding + atom.size;
|
||||
sect.@"align" = @maximum(sect.@"align", atom.alignment);
|
||||
const aligned_end_addr = mem.alignForwardGeneric(u64, section.header.size, atom_alignment);
|
||||
const padding = aligned_end_addr - section.header.size;
|
||||
section.header.size += padding + atom.size;
|
||||
section.header.@"align" = @maximum(section.header.@"align", atom.alignment);
|
||||
|
||||
if (atom.next) |next| {
|
||||
atom = next;
|
||||
} else break;
|
||||
}
|
||||
|
||||
macho_file.sections.set(match, section);
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,9 @@ pub fn getLibraryOffset(reader: anytype, cpu_arch: std.Target.Cpu.Arch) !u64 {
|
||||
return fat_arch.offset;
|
||||
}
|
||||
} else {
|
||||
log.err("Could not find matching cpu architecture in fat library: expected {}", .{cpu_arch});
|
||||
log.err("Could not find matching cpu architecture in fat library: expected {s}", .{
|
||||
@tagName(cpu_arch),
|
||||
});
|
||||
return error.MismatchedCpuArchitecture;
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,22 +90,6 @@ test "nested bitcast" {
|
||||
comptime try S.foo(42);
|
||||
}
|
||||
|
||||
test "@bitCast enum to its integer type" {
|
||||
const SOCK = enum(c_int) {
|
||||
A,
|
||||
B,
|
||||
|
||||
fn testBitCastExternEnum() !void {
|
||||
var SOCK_DGRAM = @This().B;
|
||||
var sock_dgram = @bitCast(c_int, SOCK_DGRAM);
|
||||
try expect(sock_dgram == 1);
|
||||
}
|
||||
};
|
||||
|
||||
try SOCK.testBitCastExternEnum();
|
||||
comptime try SOCK.testBitCastExternEnum();
|
||||
}
|
||||
|
||||
// issue #3010: compiler segfault
|
||||
test "bitcast literal [4]u8 param to u32" {
|
||||
const ip = @bitCast(u32, [_]u8{ 255, 255, 255, 255 });
|
||||
|
||||
@ -6,6 +6,8 @@ const expectEqual = std.testing.expectEqual;
|
||||
const native_endian = builtin.cpu.arch.endian();
|
||||
|
||||
test "correct size of packed structs" {
|
||||
// Stage2 has different packed struct semantics.
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest;
|
||||
const T1 = packed struct { one: u8, three: [3]u8 };
|
||||
|
||||
try expectEqual(4, @sizeOf(T1));
|
||||
@ -118,18 +120,6 @@ test "flags in packed structs" {
|
||||
try expectEqual(32, @bitSizeOf(Flags3));
|
||||
}
|
||||
|
||||
test "arrays in packed structs" {
|
||||
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
||||
|
||||
const T1 = packed struct { array: [3][3]u8 };
|
||||
const T2 = packed struct { array: [9]u8 };
|
||||
|
||||
try expectEqual(@sizeOf(u72), @sizeOf(T1));
|
||||
try expectEqual(72, @bitSizeOf(T1));
|
||||
try expectEqual(@sizeOf(u72), @sizeOf(T2));
|
||||
try expectEqual(72, @bitSizeOf(T2));
|
||||
}
|
||||
|
||||
test "consistent size of packed structs" {
|
||||
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
||||
|
||||
@ -145,23 +135,15 @@ test "consistent size of packed structs" {
|
||||
try expectEqual(register_size_bits, @bitSizeOf(TxData2));
|
||||
try expectEqual(register_size_bytes, @sizeOf(TxData2));
|
||||
|
||||
const TxData3 = packed struct { a: u32, b: [3]u8 };
|
||||
const TxData4 = packed struct { a: u32, b: u24 };
|
||||
const TxData5 = packed struct { a: [3]u8, b: u32 };
|
||||
const TxData6 = packed struct { a: u24, b: u32 };
|
||||
|
||||
const expectedBitSize = 56;
|
||||
const expectedByteSize = @sizeOf(u56);
|
||||
|
||||
try expectEqual(expectedBitSize, @bitSizeOf(TxData3));
|
||||
try expectEqual(expectedByteSize, @sizeOf(TxData3));
|
||||
|
||||
try expectEqual(expectedBitSize, @bitSizeOf(TxData4));
|
||||
try expectEqual(expectedByteSize, @sizeOf(TxData4));
|
||||
|
||||
try expectEqual(expectedBitSize, @bitSizeOf(TxData5));
|
||||
try expectEqual(expectedByteSize, @sizeOf(TxData5));
|
||||
|
||||
try expectEqual(expectedBitSize, @bitSizeOf(TxData6));
|
||||
try expectEqual(expectedByteSize, @sizeOf(TxData6));
|
||||
}
|
||||
@ -234,12 +216,6 @@ test "correct sizeOf and offsets in packed structs" {
|
||||
try expectEqual(@as(u7, 0b1111010), s2.y);
|
||||
try expectEqual(@as(u24, 0xd5c71f), s2.z);
|
||||
}
|
||||
|
||||
const S = packed struct { a: u32, pad: [3]u32, b: u32 };
|
||||
|
||||
try expectEqual(16, @offsetOf(S, "b"));
|
||||
try expectEqual(128, @bitOffsetOf(S, "b"));
|
||||
try expectEqual(@sizeOf(u160), @sizeOf(S));
|
||||
}
|
||||
|
||||
test "nested packed structs" {
|
||||
|
||||
@ -105,6 +105,8 @@ test "@offsetOf" {
|
||||
}
|
||||
|
||||
test "@offsetOf packed struct, array length not power of 2 or multiple of native pointer width in bytes" {
|
||||
// Stage2 has different packed struct semantics.
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest;
|
||||
const p3a_len = 3;
|
||||
const P3 = packed struct {
|
||||
a: [p3a_len]u8,
|
||||
|
||||
@ -704,10 +704,8 @@ const FooArray24Bits = packed struct {
|
||||
};
|
||||
|
||||
test "aligned array of packed struct" {
|
||||
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
||||
// Stage2 has different packed struct semantics.
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest;
|
||||
|
||||
comptime {
|
||||
try expect(@sizeOf(FooStructAligned) == 2);
|
||||
|
||||
@ -247,26 +247,17 @@ fn add(a: i32, b: i32) i32 {
|
||||
}
|
||||
|
||||
test "Type.ErrorSet" {
|
||||
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
|
||||
try testing.expect(@Type(.{ .ErrorSet = null }) == anyerror);
|
||||
|
||||
// error sets don't compare equal so just check if they compile
|
||||
_ = @Type(@typeInfo(error{}));
|
||||
_ = @Type(@typeInfo(error{A}));
|
||||
_ = @Type(@typeInfo(error{ A, B, C }));
|
||||
_ = @Type(.{
|
||||
.ErrorSet = &[_]Type.Error{
|
||||
.{ .name = "A" },
|
||||
.{ .name = "B" },
|
||||
.{ .name = "C" },
|
||||
},
|
||||
});
|
||||
_ = @Type(.{
|
||||
.ErrorSet = &.{
|
||||
.{ .name = "C" },
|
||||
.{ .name = "B" },
|
||||
.{ .name = "A" },
|
||||
},
|
||||
});
|
||||
inline for (.{ error{}, error{A}, error{ A, B, C } }) |T| {
|
||||
const info = @typeInfo(T);
|
||||
const T2 = @Type(info);
|
||||
try testing.expect(T == T2);
|
||||
}
|
||||
}
|
||||
|
||||
test "Type.Struct" {
|
||||
@ -517,3 +508,35 @@ test "Type.Union from regular enum" {
|
||||
_ = T;
|
||||
_ = @typeInfo(T).Union;
|
||||
}
|
||||
|
||||
test "Type.Fn" {
|
||||
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
|
||||
const some_opaque = opaque {};
|
||||
const some_ptr = *some_opaque;
|
||||
const T = fn (c_int, some_ptr) callconv(.C) void;
|
||||
|
||||
{
|
||||
const fn_info = std.builtin.Type{ .Fn = .{
|
||||
.calling_convention = .C,
|
||||
.alignment = 0,
|
||||
.is_generic = false,
|
||||
.is_var_args = false,
|
||||
.return_type = void,
|
||||
.args = &.{
|
||||
.{ .is_generic = false, .is_noalias = false, .arg_type = c_int },
|
||||
.{ .is_generic = false, .is_noalias = false, .arg_type = some_ptr },
|
||||
},
|
||||
} };
|
||||
|
||||
const fn_type = @Type(fn_info);
|
||||
try std.testing.expectEqual(T, fn_type);
|
||||
}
|
||||
|
||||
{
|
||||
const fn_info = @typeInfo(T);
|
||||
const fn_type = @Type(fn_info);
|
||||
try std.testing.expectEqual(T, fn_type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
fn outer(y: u32) fn (u32) u32 {
|
||||
fn outer(y: u32) *const fn (u32) u32 {
|
||||
const st = struct {
|
||||
fn get(z: u32) u32 {
|
||||
return z + y;
|
||||
@ -13,9 +13,8 @@ export fn entry() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:4:24: error: 'y' not accessible from inner function
|
||||
// tmp.zig:3:28: note: crossed function definition here
|
||||
// tmp.zig:1:10: note: declared here
|
||||
// :4:24: error: 'y' not accessible from inner function
|
||||
// :3:9: note: crossed function definition here
|
||||
@ -9,4 +9,4 @@ export fn entry() void {
|
||||
// target=native
|
||||
//
|
||||
// :3:24: error: cannot @bitCast to 'tmp.entry.E'
|
||||
// :3:24: note: use @intToEnum for type coercion
|
||||
// :3:24: note: use @intToEnum to cast from 'u32'
|
||||
|
||||
@ -7,7 +7,8 @@ export fn foo() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:4:13: error: cannot break out of defer expression
|
||||
// :4:13: error: cannot break out of defer expression
|
||||
// :3:9: note: defer expression here
|
||||
@ -7,7 +7,8 @@ export fn foo() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:4:13: error: cannot continue out of defer expression
|
||||
// :4:13: error: cannot continue out of defer expression
|
||||
// :3:9: note: defer expression here
|
||||
9
test/cases/compile_errors/direct_struct_loop.zig
Normal file
9
test/cases/compile_errors/direct_struct_loop.zig
Normal file
@ -0,0 +1,9 @@
|
||||
const A = struct { a : A, };
|
||||
export fn entry() usize { return @sizeOf(A); }
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :1:11: error: struct 'tmp.A' depends on itself
|
||||
// :1:20: note: while checking this field
|
||||
@ -0,0 +1,35 @@
|
||||
fn returns() usize {
|
||||
return 2;
|
||||
}
|
||||
export fn f1() void {
|
||||
for ("hello") |_| returns();
|
||||
}
|
||||
export fn f2() void {
|
||||
var x: anyerror!i32 = error.Bad;
|
||||
for ("hello") |_| returns() else unreachable;
|
||||
_ = x;
|
||||
}
|
||||
export fn f3() void {
|
||||
for ("hello") |_| {} else true;
|
||||
}
|
||||
export fn f4() void {
|
||||
const foo = for ("hello") |_| returns() else true;
|
||||
_ = foo;
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :5:30: error: value of type 'usize' ignored
|
||||
// :5:30: note: all non-void values must be used
|
||||
// :5:30: note: this error can be suppressed by assigning the value to '_'
|
||||
// :9:30: error: value of type 'usize' ignored
|
||||
// :9:30: note: all non-void values must be used
|
||||
// :9:30: note: this error can be suppressed by assigning the value to '_'
|
||||
// :13:31: error: value of type 'bool' ignored
|
||||
// :13:31: note: all non-void values must be used
|
||||
// :13:31: note: this error can be suppressed by assigning the value to '_'
|
||||
// :16:42: error: value of type 'usize' ignored
|
||||
// :16:42: note: all non-void values must be used
|
||||
// :16:42: note: this error can be suppressed by assigning the value to '_'
|
||||
@ -2,7 +2,7 @@ const some_data: [100]u8 align(3) = undefined;
|
||||
export fn entry() usize { return @sizeOf(@TypeOf(some_data)); }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:1:32: error: alignment value 3 is not a power of 2
|
||||
// :1:32: error: alignment value '3' is not a power of two
|
||||
13
test/cases/compile_errors/indirect_struct_loop.zig
Normal file
13
test/cases/compile_errors/indirect_struct_loop.zig
Normal file
@ -0,0 +1,13 @@
|
||||
const A = struct { b : B, };
|
||||
const B = struct { c : C, };
|
||||
const C = struct { a : A, };
|
||||
export fn entry() usize { return @sizeOf(A); }
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :1:11: error: struct 'tmp.A' depends on itself
|
||||
// :3:20: note: while checking this field
|
||||
// :2:20: note: while checking this field
|
||||
// :1:20: note: while checking this field
|
||||
@ -9,7 +9,8 @@ export fn entry() usize {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:1:13: error: struct 'Foo' depends on itself
|
||||
// :1:13: error: struct 'tmp.Foo' depends on itself
|
||||
// :2:5: note: while checking this field
|
||||
@ -0,0 +1,16 @@
|
||||
const Foo = union {
|
||||
x: Foo,
|
||||
};
|
||||
|
||||
var foo: Foo = undefined;
|
||||
|
||||
export fn entry() usize {
|
||||
return @sizeOf(@TypeOf(foo.x));
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :1:13: error: union 'tmp.Foo' depends on itself
|
||||
// :2:5: note: while checking this field
|
||||
@ -0,0 +1,10 @@
|
||||
pub export fn entry() void {
|
||||
var y = @intToPtr([*]align(4) u8, 5);
|
||||
_ = y;
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :2:39: error: pointer type '[*]align(4) u8' requires aligned address
|
||||
@ -10,8 +10,10 @@ export fn foo2() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:3:42: error: unable to @bitCast from pointer type '*[2]u8'
|
||||
// tmp.zig:8:32: error: destination type 'u16' has size 2 but source type '[]const u8' has size 16
|
||||
// :3:42: error: cannot @bitCast from '*[2]u8'
|
||||
// :3:42: note: use @ptrToInt to cast to 'u16'
|
||||
// :8:37: error: cannot @bitCast from '[]const u8'
|
||||
// :8:37: note: use @ptrToInt to cast to 'u16'
|
||||
@ -0,0 +1,84 @@
|
||||
export fn entry1() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: anyerror,
|
||||
});
|
||||
}
|
||||
export fn entry2() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: [2]u24,
|
||||
});
|
||||
}
|
||||
export fn entry3() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: anyerror!u32,
|
||||
});
|
||||
}
|
||||
export fn entry4() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: S,
|
||||
});
|
||||
}
|
||||
export fn entry5() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: U,
|
||||
});
|
||||
}
|
||||
export fn entry6() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: ?anyerror,
|
||||
});
|
||||
}
|
||||
export fn entry7() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: enum { A, B },
|
||||
});
|
||||
}
|
||||
export fn entry8() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: fn () void,
|
||||
});
|
||||
}
|
||||
export fn entry9() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: *const fn () void,
|
||||
});
|
||||
}
|
||||
export fn entry10() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: packed struct { x: i32 },
|
||||
});
|
||||
}
|
||||
export fn entry11() void {
|
||||
_ = @sizeOf(packed struct {
|
||||
x: packed union { A: i32, B: u32 },
|
||||
});
|
||||
}
|
||||
const S = struct {
|
||||
x: i32,
|
||||
};
|
||||
const U = extern union {
|
||||
A: i32,
|
||||
B: u32,
|
||||
};
|
||||
|
||||
// error
|
||||
// backend=llvm
|
||||
// target=native
|
||||
//
|
||||
// :3:9: error: packed structs cannot contain fields of type 'anyerror'
|
||||
// :3:9: note: type has no guaranteed in-memory representation
|
||||
// :8:9: error: packed structs cannot contain fields of type '[2]u24'
|
||||
// :8:9: note: type has no guaranteed in-memory representation
|
||||
// :13:9: error: packed structs cannot contain fields of type 'anyerror!u32'
|
||||
// :13:9: note: type has no guaranteed in-memory representation
|
||||
// :18:9: error: packed structs cannot contain fields of type 'tmp.S'
|
||||
// :18:9: note: only packed structs layout are allowed in packed types
|
||||
// :56:11: note: struct declared here
|
||||
// :23:9: error: packed structs cannot contain fields of type 'tmp.U'
|
||||
// :23:9: note: only packed unions layout are allowed in packed types
|
||||
// :59:18: note: union declared here
|
||||
// :28:9: error: packed structs cannot contain fields of type '?anyerror'
|
||||
// :28:9: note: type has no guaranteed in-memory representation
|
||||
// :38:9: error: packed structs cannot contain fields of type 'fn() void'
|
||||
// :38:9: note: type has no guaranteed in-memory representation
|
||||
// :38:9: note: use '*const ' to make a function pointer type
|
||||
@ -14,7 +14,7 @@ export fn entry() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:6:30: error: packed union does not support enum tag type
|
||||
// :6:30: error: packed union does not support enum tag type
|
||||
@ -12,7 +12,9 @@ export fn entry() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:6:5: error: non-packed, non-extern struct 'Foo' not allowed in packed union; no guaranteed in-memory representation
|
||||
// :6:5: error: packed unions cannot contain fields of type 'tmp.Foo'
|
||||
// :6:5: note: only packed structs layout are allowed in packed types
|
||||
// :1:13: note: struct declared here
|
||||
@ -31,5 +31,5 @@ export fn entry() void {
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :13:16: error: no field named 'arst' in enum 'tmp.Tag__enum_264'
|
||||
// :13:16: error: no field named 'arst' in enum 'tmp.Tag__enum_266'
|
||||
// :1:13: note: enum declared here
|
||||
|
||||
@ -10,7 +10,7 @@ export fn entry() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:1:21: error: expected integer, found 'f32'
|
||||
// :1:21: error: expected integer tag type, found 'f32'
|
||||
9
test/cases/compile_errors/src_outside_function.zig
Normal file
9
test/cases/compile_errors/src_outside_function.zig
Normal file
@ -0,0 +1,9 @@
|
||||
comptime {
|
||||
@src();
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :2:5: error: @src outside function
|
||||
@ -1,8 +0,0 @@
|
||||
const A = struct { a : A, };
|
||||
export fn entry() usize { return @sizeOf(A); }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:1:11: error: struct 'A' depends on itself
|
||||
@ -1,18 +0,0 @@
|
||||
fn returns() usize {
|
||||
return 2;
|
||||
}
|
||||
export fn f1() void {
|
||||
for ("hello") |_| returns();
|
||||
}
|
||||
export fn f2() void {
|
||||
var x: anyerror!i32 = error.Bad;
|
||||
for ("hello") |_| returns() else unreachable;
|
||||
_ = x;
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:5:30: error: expression value is ignored
|
||||
// tmp.zig:9:30: error: expression value is ignored
|
||||
@ -1,10 +0,0 @@
|
||||
const A = struct { b : B, };
|
||||
const B = struct { c : C, };
|
||||
const C = struct { a : A, };
|
||||
export fn entry() usize { return @sizeOf(A); }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:1:11: error: struct 'A' depends on itself
|
||||
@ -1,10 +0,0 @@
|
||||
pub fn main() void {
|
||||
var y = @intToPtr([*]align(4) u8, 5);
|
||||
_ = y;
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:13: error: pointer type '[*]align(4) u8' requires aligned address
|
||||
@ -1,9 +0,0 @@
|
||||
comptime {
|
||||
@src();
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:5: error: @src outside function
|
||||
@ -1,22 +0,0 @@
|
||||
fn returns() usize {
|
||||
return 2;
|
||||
}
|
||||
export fn f1() void {
|
||||
while (true) returns();
|
||||
}
|
||||
export fn f2() void {
|
||||
var x: ?i32 = null;
|
||||
while (x) |_| returns();
|
||||
}
|
||||
export fn f3() void {
|
||||
var x: anyerror!i32 = error.Bad;
|
||||
while (x) |_| returns() else |_| unreachable;
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:5:25: error: expression value is ignored
|
||||
// tmp.zig:9:26: error: expression value is ignored
|
||||
// tmp.zig:13:26: error: expression value is ignored
|
||||
@ -1,74 +0,0 @@
|
||||
const A = packed struct {
|
||||
x: anyerror,
|
||||
};
|
||||
const B = packed struct {
|
||||
x: [2]u24,
|
||||
};
|
||||
const C = packed struct {
|
||||
x: [1]anyerror,
|
||||
};
|
||||
const D = packed struct {
|
||||
x: [1]S,
|
||||
};
|
||||
const E = packed struct {
|
||||
x: [1]U,
|
||||
};
|
||||
const F = packed struct {
|
||||
x: ?anyerror,
|
||||
};
|
||||
const G = packed struct {
|
||||
x: Enum,
|
||||
};
|
||||
export fn entry1() void {
|
||||
var a: A = undefined;
|
||||
_ = a;
|
||||
}
|
||||
export fn entry2() void {
|
||||
var b: B = undefined;
|
||||
_ = b;
|
||||
}
|
||||
export fn entry3() void {
|
||||
var r: C = undefined;
|
||||
_ = r;
|
||||
}
|
||||
export fn entry4() void {
|
||||
var d: D = undefined;
|
||||
_ = d;
|
||||
}
|
||||
export fn entry5() void {
|
||||
var e: E = undefined;
|
||||
_ = e;
|
||||
}
|
||||
export fn entry6() void {
|
||||
var f: F = undefined;
|
||||
_ = f;
|
||||
}
|
||||
export fn entry7() void {
|
||||
var g: G = undefined;
|
||||
_ = g;
|
||||
}
|
||||
const S = struct {
|
||||
x: i32,
|
||||
};
|
||||
const U = struct {
|
||||
A: i32,
|
||||
B: u32,
|
||||
};
|
||||
const Enum = enum {
|
||||
A,
|
||||
B,
|
||||
};
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
// is_test=1
|
||||
//
|
||||
// tmp.zig:2:5: error: type 'anyerror' not allowed in packed struct; no guaranteed in-memory representation
|
||||
// tmp.zig:5:5: error: array of 'u24' not allowed in packed struct due to padding bits (must be padded from 48 to 64 bits)
|
||||
// tmp.zig:8:5: error: type 'anyerror' not allowed in packed struct; no guaranteed in-memory representation
|
||||
// tmp.zig:11:5: error: non-packed, non-extern struct 'S' not allowed in packed struct; no guaranteed in-memory representation
|
||||
// tmp.zig:14:5: error: non-packed, non-extern struct 'U' not allowed in packed struct; no guaranteed in-memory representation
|
||||
// tmp.zig:17:5: error: type '?anyerror' not allowed in packed struct; no guaranteed in-memory representation
|
||||
// tmp.zig:20:5: error: type 'Enum' not allowed in packed struct; no guaranteed in-memory representation
|
||||
// tmp.zig:57:14: note: enum declaration does not specify an integer tag type
|
||||
@ -11,9 +11,9 @@ export fn entry() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:1:17: error: struct 'LhsExpr' depends on itself
|
||||
// tmp.zig:5:5: note: while checking this field
|
||||
// tmp.zig:2:5: note: while checking this field
|
||||
// :1:17: error: struct 'tmp.LhsExpr' depends on itself
|
||||
// :5:5: note: while checking this field
|
||||
// :2:5: note: while checking this field
|
||||
@ -6,7 +6,8 @@ export fn entry() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:19: error: dependency loop detected
|
||||
// :1:1: error: dependency loop detected
|
||||
// :2:19: note: referenced here
|
||||
@ -7,5 +7,5 @@ export fn entry() foo {
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :1:1: error: variable of type 'type' must be const or comptime
|
||||
// :1:1: note: types are not available at runtime
|
||||
// :1:5: error: variable of type 'type' must be const or comptime
|
||||
// :1:5: note: types are not available at runtime
|
||||
|
||||
@ -7,7 +7,7 @@ export fn entry() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:15: error: unused variable: 'a'
|
||||
// :2:15: error: unused capture
|
||||
@ -8,5 +8,5 @@ export fn entry() void {
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :1:1: error: variable of type 'comptime_int' must be const or comptime
|
||||
// :1:1: note: to modify this variable at runtime, it must be given an explicit fixed-size number type
|
||||
// :1:5: error: variable of type 'comptime_int' must be const or comptime
|
||||
// :1:5: note: to modify this variable at runtime, it must be given an explicit fixed-size number type
|
||||
|
||||
@ -4,4 +4,4 @@ usingnamespace void;
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :1:1: error: type void has no namespace
|
||||
// :1:16: error: type void has no namespace
|
||||
|
||||
@ -4,7 +4,7 @@ export fn entry() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:62: error: index 3 outside vector of size 3
|
||||
// :2:49: error: expected 3 vector elements; found 4
|
||||
@ -4,7 +4,7 @@ export fn foo() void {
|
||||
fn bar() bool { return true; }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:15: error: expected error union type, found 'bool'
|
||||
// :2:15: error: expected error union type, found 'bool'
|
||||
@ -4,7 +4,7 @@ export fn foo() void {
|
||||
fn bar() ?i32 { return 1; }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:15: error: expected error union type, found '?i32'
|
||||
// :2:15: error: expected error union type, found '?i32'
|
||||
@ -4,7 +4,7 @@ export fn foo() void {
|
||||
fn bar() bool { return true; }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:15: error: expected optional type, found 'bool'
|
||||
// :2:15: error: expected optional type, found 'bool'
|
||||
@ -4,7 +4,7 @@ export fn foo() void {
|
||||
fn bar() anyerror!i32 { return 1; }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:15: error: expected optional type, found 'anyerror!i32'
|
||||
// :2:15: error: expected optional type, found 'anyerror!i32'
|
||||
@ -0,0 +1,43 @@
|
||||
fn returns() usize {
|
||||
return 2;
|
||||
}
|
||||
export fn f1() void {
|
||||
while (true) returns();
|
||||
}
|
||||
export fn f2() void {
|
||||
var x: ?i32 = null;
|
||||
while (x) |_| returns();
|
||||
}
|
||||
export fn f3() void {
|
||||
var x: anyerror!i32 = error.Bad;
|
||||
while (x) |_| returns() else |_| unreachable;
|
||||
}
|
||||
export fn f4() void {
|
||||
var a = true;
|
||||
while (a) {} else true;
|
||||
}
|
||||
export fn f5() void {
|
||||
var a = true;
|
||||
const foo = while (a) returns() else true;
|
||||
_ = foo;
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :5:25: error: value of type 'usize' ignored
|
||||
// :5:25: note: all non-void values must be used
|
||||
// :5:25: note: this error can be suppressed by assigning the value to '_'
|
||||
// :9:26: error: value of type 'usize' ignored
|
||||
// :9:26: note: all non-void values must be used
|
||||
// :9:26: note: this error can be suppressed by assigning the value to '_'
|
||||
// :13:26: error: value of type 'usize' ignored
|
||||
// :13:26: note: all non-void values must be used
|
||||
// :13:26: note: this error can be suppressed by assigning the value to '_'
|
||||
// :17:23: error: value of type 'bool' ignored
|
||||
// :17:23: note: all non-void values must be used
|
||||
// :17:23: note: this error can be suppressed by assigning the value to '_'
|
||||
// :21:34: error: value of type 'usize' ignored
|
||||
// :21:34: note: all non-void values must be used
|
||||
// :21:34: note: this error can be suppressed by assigning the value to '_'
|
||||
@ -0,0 +1,21 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
|
||||
_ = stack_trace;
|
||||
if (std.mem.eql(u8, message, "sentinel mismatch: expected tmp.main.S{ .a = 1 }, found tmp.main.S{ .a = 2 }")) {
|
||||
std.process.exit(0);
|
||||
}
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
const S = struct { a: u32 };
|
||||
var arr = [_]S{ .{ .a = 1 }, .{ .a = 2 } };
|
||||
var s = arr[0..1 :.{ .a = 1 }];
|
||||
_ = s;
|
||||
return error.TestFailed;
|
||||
}
|
||||
|
||||
// run
|
||||
// backend=llvm
|
||||
// target=native
|
||||
19
test/cases/safety/array slice sentinel mismatch vector.zig
Normal file
19
test/cases/safety/array slice sentinel mismatch vector.zig
Normal file
@ -0,0 +1,19 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
|
||||
_ = stack_trace;
|
||||
if (std.mem.eql(u8, message, "sentinel mismatch: expected { 0, 0 }, found { 4, 4 }")) {
|
||||
std.process.exit(0);
|
||||
}
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var buf: [4]@Vector(2, u32) = .{ .{ 1, 1 }, .{ 2, 2 }, .{ 3, 3 }, .{ 4, 4 } };
|
||||
const slice = buf[0..3 :.{ 0, 0 }];
|
||||
_ = slice;
|
||||
return error.TestFailed;
|
||||
}
|
||||
// run
|
||||
// backend=llvm
|
||||
// target=native
|
||||
@ -2,17 +2,18 @@ const std = @import("std");
|
||||
|
||||
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
|
||||
_ = stack_trace;
|
||||
if (std.mem.eql(u8, message, "sentinel mismatch")) {
|
||||
if (std.mem.eql(u8, message, "sentinel mismatch: expected 0, found 4")) {
|
||||
std.process.exit(0);
|
||||
}
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var buf: [4]u8 = undefined;
|
||||
var buf: [4]u8 = .{ 1, 2, 3, 4 };
|
||||
const slice = buf[0..3 :0];
|
||||
_ = slice;
|
||||
return error.TestFailed;
|
||||
}
|
||||
// run
|
||||
// backend=stage1
|
||||
// backend=llvm
|
||||
// target=native
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user