Commit b91d6ff9e8

Veikka Tuominen <git@vexu.eu>
2023-04-10 17:48:23
add runtime safety for noreturn function returning
Closes #15221
1 parent 2286c19
Changed files (4)
lib
src
test
lib/std/builtin.zig
@@ -1006,6 +1006,7 @@ pub const panic_messages = struct {
     pub const for_len_mismatch = "for loop over objects with non-equal lengths";
     pub const memcpy_len_mismatch = "@memcpy arguments have non-equal lengths";
     pub const memcpy_alias = "@memcpy arguments alias";
+    pub const noreturn_returned = "'noreturn' function returned";
 };
 
 pub noinline fn returnError(st: *StackTrace) void {
src/codegen/llvm.zig
@@ -5030,7 +5030,6 @@ pub const FuncGen = struct {
         }
 
         if (return_type.isNoReturn() and attr != .AlwaysTail) {
-            _ = self.builder.buildUnreachable();
             return null;
         }
 
src/Sema.zig
@@ -7055,15 +7055,38 @@ fn analyzeCall(
             } },
         });
         sema.appendRefsAssumeCapacity(args);
+
+        if (call_tag == .call_always_tail) {
+            if (ensure_result_used) {
+                try sema.ensureResultUsed(block, sema.typeOf(func_inst), call_src);
+            }
+            return sema.handleTailCall(block, call_src, func_ty, func_inst);
+        } else if (block.wantSafety() and func_ty_info.return_type.isNoReturn()) {
+            // Function pointers and extern functions aren't guaranteed to
+            // actually be noreturn so we add a safety check for them.
+            check: {
+                var func_val = (try sema.resolveMaybeUndefVal(func)) orelse break :check;
+                switch (func_val.tag()) {
+                    .function, .decl_ref => {
+                        _ = try block.addNoOp(.unreach);
+                        return Air.Inst.Ref.unreachable_value;
+                    },
+                    else => break :check,
+                }
+            }
+
+            try sema.safetyPanic(block, .noreturn_returned);
+            return Air.Inst.Ref.unreachable_value;
+        } else if (func_ty_info.return_type.isNoReturn()) {
+            _ = try block.addNoOp(.unreach);
+            return Air.Inst.Ref.unreachable_value;
+        }
         break :res func_inst;
     };
 
     if (ensure_result_used) {
         try sema.ensureResultUsed(block, sema.typeOf(result), call_src);
     }
-    if (call_tag == .call_always_tail) {
-        return sema.handleTailCall(block, call_src, func_ty, result);
-    }
     return result;
 }
 
@@ -7556,6 +7579,10 @@ fn instantiateGenericCall(
     if (call_tag == .call_always_tail) {
         return sema.handleTailCall(block, call_src, func_ty, result);
     }
+    if (new_fn_info.return_type.isNoReturn()) {
+        _ = try block.addNoOp(.unreach);
+        return Air.Inst.Ref.unreachable_value;
+    }
     return result;
 }
 
@@ -23441,6 +23468,7 @@ pub const PanicId = enum {
     for_len_mismatch,
     memcpy_len_mismatch,
     memcpy_alias,
+    noreturn_returned,
 };
 
 fn addSafetyCheck(
test/cases/safety/noreturn returned.zig
@@ -0,0 +1,23 @@
+const std = @import("std");
+
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+    _ = stack_trace;
+    if (std.mem.eql(u8, message, "'noreturn' function returned")) {
+        std.process.exit(0);
+    }
+    std.process.exit(1);
+}
+const T = struct {
+    export fn bar() void {
+        // ...
+    }
+};
+
+extern fn bar() noreturn;
+pub fn main() void {
+    _ = T.bar;
+    bar();
+}
+// run
+// backend=llvm
+// target=native