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

Enum value wrong in nested switch #17718

Open
kuon opened this issue Oct 26, 2023 · 7 comments
Open

Enum value wrong in nested switch #17718

kuon opened this issue Oct 26, 2023 · 7 comments
Labels
bug Observed behavior contradicts documented or intended behavior

Comments

@kuon
Copy link
Contributor

kuon commented Oct 26, 2023

Zig Version

0.12.0-dev.1270+6c9d34bce

Steps to Reproduce and Observed Behavior

I have code similar to this: (I was unable to reproduce the bug with a minimal example)

const std = @import("std");

fn BuildId(comptime name: []const u8) type {
    return @Type(.{ .Struct = .{
        .layout = .Auto,
        .fields = &.{
            .{
                .name = name,
                .type = i64,
                .default_value = null,
                .is_comptime = false,
                .alignment = 0,
            },
        },
        .decls = &.{},
        .is_tuple = false,
    } });
}

// Actual code has 20 id types
pub const IdentityId = BuildId("identity_id");
pub const AccountId = BuildId("account_id");

// Actual code has 20 members
pub const Id = union(enum) {
    identity_id: IdentityId,
    account_id: AccountId,
};

// Actual code has about a hundred members
pub const Msg = union(enum) {
    ok,
    id: Id,
};

pub fn main() !void {
    var msg: Msg = .{ .id = .{ .account_id = .{ .account_id = 10 } } };

    std.debug.print("msg {any}\n", .{msg});
    std.debug.print("tag A {}\n", .{@intFromEnum(msg.id)});
    switch (msg) {
        .id => |id| {
            std.debug.print("ID {any}\n", .{id});
            std.debug.print("tag B {}\n", .{@intFromEnum(id)});
            switch (id) {
                .account_id => std.debug.print("yes\n", .{}),
                else => std.debug.print("no\n", .{}),
            }
        },
        else => std.debug.print("no\n", .{}),
    }
}

The tag should be the same both time, it should print tag A 1 and tag B 1.

But in my code, the second one is 0.

Here is the actual code and it's output:

        var r1 = try self.send_recv(Msg{ .register_endpoint = params });
        defer r1.deinit();

        std.debug.print("msg {any}\n", .{r1.msg});
        std.debug.print("tag A {}\n", .{@intFromEnum(r1.msg.id)});
        switch (r1.msg) {
            .id => |id| {
                std.debug.print("ID {any}\n", .{id});
                std.debug.print("tag B {}\n", .{@intFromEnum(id)});
                switch (id) {
                    .endpoint_id => |endpoint_id| return endpoint_id,
                    else => return error.BadId,
                }
            },
            else => return error.BadMsg,
        }
msg msg.Msg{ .id = client.Id{ .endpoint_id = client.BuildId("endpoint_id"){ .endpoint_id = 1 } } }
tag A 12
ID client.Id{ .identity_id = client.BuildId("identity_id"){ .identity_id = 1 } }
tag B 0

If I do .id => |*id| and switch (id.*) the bug does not occurs.

Actual code is built for wasm.

Expected Behavior

Enum tag should be the same within the switch.

@kuon kuon added the bug Observed behavior contradicts documented or intended behavior label Oct 26, 2023
@kuon
Copy link
Contributor Author

kuon commented Oct 26, 2023

I did a few more tests:

The code does exhibit the bug only when run in the browser in wasm.

I suspected some weird memory corruption, so I replaced the line with send_recv (which does some allocation) with var msg: Msg = .{ .id = .{ .endpoint_id = .{ .endpoint_id = 23 } } }; and replaced r1.msg with msg and it does trigger the bug, but in this case, even the first debug prints tag A 0

@kuon
Copy link
Contributor Author

kuon commented Oct 26, 2023

Ok, I discovered something.

I noticed that the enum is actually correct up to 4 and then it starts back to 0. My endpoint_id was actually 12 in the enum, if I move it to 13 I get 1, and if I move it to 11 I get 4.

It made me realize that the enum underlying type must be incorrectly inferred under some circumstances.

If I explicitly specify u8 as enum type, so

// from 
pub const Id = union(enum) {
// to 
pub const Id = union(enum(u8)) {

The bug does not happen.

@Vexu
Copy link
Member

Vexu commented Oct 26, 2023

Cannot reproduce on x86-64_linux:

msg a.Msg{ .id = a.Id{ .account_id = a.BuildId("account_id"){ .account_id = 10 } } }
tag A 1
ID a.Id{ .account_id = a.BuildId("account_id"){ .account_id = 10 } }
tag B 1
yes

@kuon
Copy link
Contributor Author

kuon commented Oct 26, 2023

Yes I cannot reproduce it outside of the browser with my own code base.

@Vexu
Copy link
Member

Vexu commented Oct 26, 2023

Ah, I didn't notice the line about wasm. Either way I can't reproduce it using wasmtime either, can you share your js code for it?

@kuon
Copy link
Contributor Author

kuon commented Oct 26, 2023

Yeah I cannot reproduce it with command line wasm runtime either. I cannot share the JS code as it is part of a proprietary plugin loading system. I'm trying to extract the most important bits of it to get a reproduction.

@kuon
Copy link
Contributor Author

kuon commented Oct 26, 2023

I spent the day trying to reproduce it, without success. I tried to compare the .wasm but it was very hard to get anything out of it. Changing pub const Id = union(enum) { to pub const Id = union(enum(u8)) { generates much different output with about 10kB size difference (on a 2.4MB file).

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
Projects
None yet
Development

No branches or pull requests

2 participants