Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler will out-of-memory with noreturn error union #3461

Closed
MasterQ32 opened this issue Oct 16, 2019 · 13 comments · Fixed by #12462
Closed

Compiler will out-of-memory with noreturn error union #3461

MasterQ32 opened this issue Oct 16, 2019 · 13 comments · Fixed by #12462
Labels
bug Observed behavior contradicts documented or intended behavior stage1 The process of building from source via WebAssembly and the C backend.
Milestone

Comments

@MasterQ32
Copy link
Contributor

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;
}
@daurnimator daurnimator added the bug Observed behavior contradicts documented or intended behavior label Oct 16, 2019
@andrewrk andrewrk added the stage1 The process of building from source via WebAssembly and the C backend. label Oct 23, 2019
@andrewrk andrewrk added this to the 0.6.0 milestone Oct 23, 2019
@daurnimator
Copy link
Collaborator

@LemonBoy

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

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

@LemonBoy
Copy link
Contributor

LemonBoy commented Mar 9, 2020

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.

@MasterQ32
Copy link
Contributor Author

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)

@LemonBoy
Copy link
Contributor

#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.

@andrewrk andrewrk modified the milestones: 0.6.0, 0.7.0 Mar 12, 2020
@marler8997
Copy link
Contributor

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
}

@ghost
Copy link

ghost commented Mar 19, 2020

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.

@marler8997
Copy link
Contributor

marler8997 commented Mar 20, 2020

@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.

@WoodyAtHome
Copy link

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.

@LemonBoy LemonBoy mentioned this issue Oct 12, 2020
@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 Nov 6, 2020
@andrewrk andrewrk modified the milestones: 0.9.0, 0.10.0 May 19, 2021
tauoverpi added a commit to tauoverpi/how-to-exit-vim that referenced this issue Jan 7, 2022
@Vexu Vexu changed the title Compiler will out-of-memory with certain program Compiler will out-of-memory with noreturn error union Jan 29, 2022
@nektro
Copy link
Contributor

nektro commented Jan 29, 2022

using the signature !noreturn should be an immediate compile error

@WoodyAtHome
Copy link

@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))

@nektro
Copy link
Contributor

nektro commented Jan 30, 2022

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

@Vexu
Copy link
Member

Vexu commented Jan 30, 2022

!noreturn has been accepted in #3257.

@Techcable
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Observed behavior contradicts documented or intended behavior stage1 The process of building from source via WebAssembly and the C backend.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants