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

Comptime conversion of T to E!T where T is a tagged union gives the wrong result #1442

Closed
hcff opened this Issue Aug 30, 2018 · 0 comments

Comments

Projects
None yet
3 participants
@hcff
Contributor

hcff commented Aug 30, 2018

Here's a program :

const std = @import("std");

const Union = union(enum) {
    Text: []const u8,
    Color: u32,
};

pub fn main() void {
    const union_ = Union{ .Color = 0xFFFF00FF };
    const union_or_err = (error{SomeError}!Union)(union_);
    printBytes(union_);
    printBytes(union_or_err);
    std.debug.warn("union_ : {}\n", union_);
    std.debug.warn("union_or_err : {}\n", union_or_err);
}

fn printBytes(v: var) void {
    const T = @typeOf(v);
    for (@sliceToBytes(([]T{v})[0..])) |b, i| {
        std.debug.warn("{x2}", b);
        if (i % 4 == 3) {
            std.debug.warn(" ");
        }
    }
    std.debug.warn("\n");
}

This program crashes while trying to print union_or_err because its value is corrupt. Specifically the payload is not copied at the right place. This corruption only happens if the conversion is done at comptime.

ff00ffff 00000000 00000000 00000000 01000000 00000000 
00000000 ff00ffff 00000000 00000000 00000000 01000000 007b7d20 7b7d207b 
union_ : Union{ .Color = 4294902015 }
union_or_err : Union{ .Text = reached unreachable code
zig/lib/zig/std/os/index.zig:348:47: 0x2066d0 in ??? (run)
                posix.EINVAL, posix.EFAULT => unreachable,
                                              ^
zig/lib/zig/std/os/file.zig:412:30: 0x20651b in ??? (run)
            try os.posixWrite(self.handle, bytes);
                             ^
zig/lib/zig/std/io.zig:73:31: 0x205a28 in ??? (run)
        return self.file.write(bytes);
                              ^
zig/lib/zig/std/fmt/index.zig:233:30: 0x223a04 in ??? (run)
                return output(context, casted_value);
                             ^
zig/lib/zig/std/fmt/index.zig:197:47: 0x223c9a in ??? (run)
                                try formatType(@field(value, u_field.name), "", context, Errors, output);
                                              ^
zig/lib/zig/std/fmt/index.zig:137:34: 0x22372f in ??? (run)
                return formatType(payload, fmt, context, Errors, output);
                                 ^
zig/lib/zig/std/fmt/index.zig:53:35: 0x22366f in ??? (run)
                    try formatType(args[next_arg], fmt[0..0], context, Errors, output);
                                  ^
zig/lib/zig/std/io.zig:227:34: 0x222f0b in ??? (run)
            return std.fmt.format(self, Error, self.writeFn, format, args);
                                 ^
zig/lib/zig/std/debug/index.zig:32:17: 0x222aa2 in ??? (run)
    stderr.print(fmt, args) catch return;
                ^
uniontest/uniontest.zig:20:19: 0x22254a in ??? (run)
    std.debug.warn("union_or_err : {}\n", union_or_err);
                  ^
zig/lib/zig/std/special/bootstrap.zig:86:22: 0x222489 in ??? (run)
            root.main();
                     ^
zig/lib/zig/std/special/bootstrap.zig:70:20: 0x222435 in ??? (run)
    return callMain();
                   ^
zig/lib/zig/std/special/bootstrap.zig:64:39: 0x222298 in ??? (run)
    std.os.posix.exit(callMainWithArgs(argc, argv, envp));
                                      ^
zig/lib/zig/std/special/bootstrap.zig:37:5: 0x222150 in ??? (run)
    @noInlineCall(posixCallMainAndExit);
    ^

The unreachable is because warn thinks the .Text field is active, and try to print the content of the byte slice (which has a null pointer since it's corrupt), which cause write() to return EFAULT.

What seems to be the problem is that the conversion offset the payload by 4 bytes, when the error + padding is 8 bytes long.

If the line const union_ = Union{ .Color = 0xFFFF00FF }; is changed to var union_ = ..., then this works just fine because the runtime conversion works correctly, and the union is shifted by 8 bytes.

ff00ffff 00000000 00000000 00000000 01000000 007b7d20 
00000000 00000000 ff00ffff 00000000 00000000 00000000 01000000 007b7d20 
union_ : Union{ .Color = 4294902015 }
union_or_err : Union{ .Color = 4294902015 }

If the line Text: []const u8, is changed to just Text,, then it also works.
The alignment of Union is in this case 4 bytes, so the error part of the error union only takes 4 bytes, so offsetting the payload by that amount is the correct behavior.

ff00ffff 01000000 
00000000 ff00ffff 01000000 
union_ : Union{ .Color = 4294902015 }
union_or_err : Union{ .Color = 4294902015 }

For some reason, this bug doesn't happen with a u64 instead of Union, even though they have the same alignment.

    const int: u64 = 0x0123456789ABCDEF;
    const int_or_err = (error{SomeError}!u64)(int);
    printBytes(int);
    printBytes(int_or_err);
    std.debug.warn("int : {}\n", int);
    std.debug.warn("int_or_err : {}\n", int_or_err);
efcdab89 67452301 
00000000 00000000 efcdab89 67452301 
int : 81985529216486895
int_or_err : 81985529216486895

@tiehuis tiehuis added the bug label Aug 31, 2018

@andrewrk andrewrk added this to the 0.3.0 milestone Sep 2, 2018

@andrewrk andrewrk closed this in 7dd3c38 Sep 11, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment