ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.

Home Page:https://ziglang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Compiler will out-of-memory with noreturn error union

ikskuh opened this issue · comments

When compiling the following program, the zig compiler will allocate a lot (> 24 GB) memory and will be killed by the OS. This is probably to some infinite loop.

extern fn someData() bool;

fn loop() !noreturn {
    while(true) {
        if(someData())
            return error.GenericFailure;
    }
}

export fn square() bool {
    return loop() catch false;
}

@LemonBoy

Duplicate of #3461, the !noreturn combo should be disallowed.

Why?
Seems like a sensible return signature for something like execv?

Why?

The ! implies you may return an error, noreturn implies the function doesn't ever return.
If the noreturn is passed down to LLVM shit may hit the fan:

This function attribute indicates that the function never returns normally, hence through a return instruction. This produces undefined behavior at runtime if the function ever does dynamically return. Annotated functions may still raise an exception, i.a., nounwind is not implied.

I don't think we should try to fit the LLVM detail here, because the message conveyed by !noreturn is exactly what you said:

The ! implies you may return an error, noreturn implies the function doesn't ever return.

The function will either never return or throw an error, if so. So the whole non-catching part could be marked as unreachable by the compiler and emit the "code unreachable" error message.

Use case is pretty much any program that is not designed to ever quit working (like firmware or such), but may throw an error that is not a panic and need a soft-recover the system (as opposed to panics which are by-definition not recoverable)

#3263 is also a duplicate of this issue.

The f() catch val generates a pointer to noreturn that sends the analysis phase in a endless loop. Fixing this is quite tricky as we go directly from AST to a set of IR instructions that "explode" the error union when we don't know yet the type of f() nor of val.

This is another example where a multi-pass analysis design would have helped: the catch is desugared into a IrCatchInst and once its operands are analyzed we can selectively unwrap the pieces we need.

I tried using !noreturn today as I had a use case for it and it completely locked up my PC forcing me to restart it :(

I looked up to see if this was an existing issue and found this thread.

The usecase I had for it is I have an event loop function with sockets that's supposed to run forever unless it fails. If it returns an error, the calling function will re-initialize and then call the function again, i.e.

pub fn main() void {
    while (true) {
        const sock = connect(host);
        defer os.close(sock);
        eventLoop() catch |e| switch (e) {
            error.Disconnected => continue, // ignore error and reconnect
        };
    }
}

fn eventLoop(fd: fd_t) !noreturn {
    // ...
    // if we detect disconntion
    return error.Disconnected; // caller will connect again and restart the event loop
}

Would using error{Something} as a return type work for this use case? (a function that can only return an error.) You won't be able to use try or catch when calling it though, since the function isn't technically returning an error union.

@dbandstra That seems to work to a point. The only issue I'm seeing is that it forces me to maintain the set of errors for each function that does this rather than being able to use inferred error sets. I've been finding that inferred error are a very nice feature.

Hi, just a thought: Maybe it isn't so much work to detect return type !noreturn and then stop the compiler with a message?
The final fix can be done later, but with the current compiler behaviour I had to turn the PC off, lost a lot of data and so on. Even worse the HDD could be messed up. Very dangerous.

using the signature !noreturn should be an immediate compile error

@nektro: What main signature would you suggest for a program that never should end (e.g. a a small program on a mcu for a room temperature sensor or a web server)? MasterQ32 explained also why !noreturn makes sense: (#3461 (comment))

either !void or bare noreturn. !noreturn is a contradiction

I use the former in my web server here https://github.com/nektro/aquila/blob/r21/src/main.zig#L27

!noreturn has been accepted in #3257.

Another (longer) way I ran into with specific error types: https://gist.github.com/Techcable/88808f0ff219e22f6bfd02ce1dbae0d9