From 2c184f9a5fc78be4f38cc74106c203b7bc80deb4 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 Jul 2022 00:43:11 +0200 Subject: [PATCH] link-tests: add checkNotPresent and add -dead_strip smoke test `checkNotPresent` is the inverse of `checkNext` - if the phrase is found in the output, then it fails the test. --- lib/std/build/CheckObjectStep.zig | 35 ++++++++++++++++++-- test/link.zig | 4 +++ test/link/macho/dead_strip/build.zig | 49 ++++++++++++++++++++++++++++ test/link/macho/dead_strip/main.c | 14 ++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 test/link/macho/dead_strip/build.zig create mode 100644 test/link/macho/dead_strip/main.c diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index cb91f883c9..b807e1de45 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -50,7 +50,7 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe /// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively /// they could then be added with this simple program `vmaddr entryoff +`. const Action = struct { - tag: enum { match, compute_cmp }, + tag: enum { match, not_present, compute_cmp }, phrase: []const u8, expected: ?ComputeCompareExpected = null, @@ -63,7 +63,7 @@ const Action = struct { /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` /// in that order with other letters in between fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool { - assert(act.tag == .match); + assert(act.tag == .match or act.tag == .not_present); var candidate_var: ?struct { name: []const u8, value: u64 } = null; var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); @@ -202,6 +202,13 @@ const Check = struct { }) catch unreachable; } + fn notPresent(self: *Check, phrase: []const u8) void { + self.actions.append(.{ + .tag = .not_present, + .phrase = self.builder.dupe(phrase), + }) catch unreachable; + } + fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void { self.actions.append(.{ .tag = .compute_cmp, @@ -226,6 +233,15 @@ pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { last.match(phrase); } +/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)` +/// however ensures there is no matching phrase in the output. +/// Asserts at least one check already exists. +pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.notPresent(phrase); +} + /// Creates a new check checking specifically symbol table parsed and dumped from the object /// file. /// Issuing this check will force parsing and dumping of the symbol table. @@ -293,6 +309,21 @@ fn make(step: *Step) !void { return error.TestFailed; } }, + .not_present => { + while (it.next()) |line| { + if (try act.match(line, &vars)) { + std.debug.print( + \\ + \\========= Expected not to find: =================== + \\{s} + \\========= But parsed file does contain it: ======== + \\{s} + \\ + , .{ act.phrase, output }); + return error.TestFailed; + } + } + }, .compute_cmp => { const res = act.computeCmp(gpa, vars) catch |err| switch (err) { error.UnknownVariable => { diff --git a/test/link.zig b/test/link.zig index f7bd70fa66..afab1852eb 100644 --- a/test/link.zig +++ b/test/link.zig @@ -60,6 +60,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .build_modes = true, }); + cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ + .build_modes = false, + }); + cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{ .build_modes = true, .requires_macos_sdk = true, diff --git a/test/link/macho/dead_strip/build.zig b/test/link/macho/dead_strip/build.zig new file mode 100644 index 0000000000..5b063308b5 --- /dev/null +++ b/test/link/macho/dead_strip/build.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +const Builder = std.build.Builder; +const LibExeObjectStep = std.build.LibExeObjStep; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test the program"); + test_step.dependOn(b.getInstallStep()); + + { + // Without -dead_strip, we expect `iAmUnused` symbol present + const exe = createScenario(b, mode); + + const check = exe.checkObject(.macho); + check.checkInSymtab(); + check.checkNext("{*} (__TEXT,__text) external _iAmUnused"); + + test_step.dependOn(&check.step); + + const run_cmd = exe.run(); + run_cmd.expectStdOutEqual("Hello!\n"); + test_step.dependOn(&run_cmd.step); + } + + { + // With -dead_strip, no `iAmUnused` symbol should be present + const exe = createScenario(b, mode); + exe.link_gc_sections = true; + + const check = exe.checkObject(.macho); + check.checkInSymtab(); + check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused"); + + test_step.dependOn(&check.step); + + const run_cmd = exe.run(); + run_cmd.expectStdOutEqual("Hello!\n"); + test_step.dependOn(&run_cmd.step); + } +} + +fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep { + const exe = b.addExecutable("test", null); + exe.addCSourceFile("main.c", &[0][]const u8{}); + exe.setBuildMode(mode); + exe.linkLibC(); + return exe; +} diff --git a/test/link/macho/dead_strip/main.c b/test/link/macho/dead_strip/main.c new file mode 100644 index 0000000000..4756e2ca13 --- /dev/null +++ b/test/link/macho/dead_strip/main.c @@ -0,0 +1,14 @@ +#include + +void printMe() { + printf("Hello!\n"); +} + +int main(int argc, char* argv[]) { + printMe(); + return 0; +} + +void iAmUnused() { + printf("YOU SHALL NOT PASS!\n"); +}