`undefined`: Can this be expected to work?

The documentation says this about undefined:

In Debug mode, Zig writes 0xaa bytes to undefined memory. This is to catch bugs early, and to help detect use of undefined memory in a debugger. However, this behavior is only an implementation feature, not a language semantic, so it is not guaranteed to be observable to code.

I find myself wondering if this can (carefully) be promoted to a language semantic. Not this exact thing, but something like it.

Most of the time, we can do this:

threadlocal var some_ptr: *Pointer = undefined;

// Forgive the 'style violation'
const ILLEGAL_ADDRESS: usize = undefined;

// Ope!, did it again
fn debug_assert(ok: bool) void {
   // Why is this still in caps btw?
   if (builtin.mode == .Debug { 
       assert(ok); 
   }
}

// Later, in a function or something
debug_assert(@intFromPtr(some_ptr) != ILLEGAL_ADDRESS);

This will mostly work most of the time.

Problem is, Zig code can’t make the assumption that an illegal address exists, certainly not that it’s 0xaaaa_aaaa on 32 bit systems.

Something like this could work though:

const can_check = builtin.mode == .Debug and builtin.illegal_ptr_address != null;
const illegal_address: ?usize = builtin.illegal_ptr_address;

threadlocal var some_ptr: *Pointer = undefined;

fn ptr_ok(ptr: usize) void {
    if (can_check) 
        assert(ptr != illegal_address.?);
}

// later
ptr_ok(@intFromPtr(some_ptr);

Basically: if there is an illegal pointer value, distinct from null, then builtin.illegal_ptr_address has that value, if not, it’s null. Then, in .Debug mode (at least?) it will be correct to check a maybe-still-undefined pointer integer value against builtin.illegal_ptr_address. Otherwise all bets are off.

Most client code this will be true, that’s why that value of undefined exists: the A Pointer is in kernel space, and you don’t get to look at it, so it’s representative of a very large space of addresses which are illegal to reference.

Basically I would like to be able to write this check, such that it only compiles if it will always work.

undefined isn’t just about pointers.

1 Like

I did not say, or mean to imply, that it is?

Hello,
so your idea, is about being able to define a range of illegal pointers that cant be referenced, or just overall being able to make a check against this ā€œlistā€.

Also i am not sure why this would be particulary desireable. You have to remember that the list included will only at the best be list of OS specific kernel address space. It wont block you from dereferencing invalid pointer and the range of kernel invalid adresses and the range of user space invalid adresses is tilted a lot to the latter.

Not to mention, its irellevant in Debug, you can implement it already (including undefined), there is no need for something like this to be a language feature and for other release modes i dont think its desireable behavior to check for undefined values (rest is again possible in userspace).

Did i understood you badly? Please explain a bit more.

Neither. The idea is that if, and only if, the debug value of a pointer set to undefined is always illegal to reference, then, and only then, builtin.illegal_ptr_address will be identical to that usize value.

Otherwise, builtin.illegal_ptr_address (which could be better named perhaps) would be null.

Do you understand now?

I haven’t really used Valgrind a lot yet, but if the usecases for this would be caught by using Valgrind or a similar tool than I don’t see a reason for this.

So I think we should try to use Valgrind (maybe even figure out some half or fully automated way to run it) instead of these manual attempts at asserting something like this.

I guess if this could be detected in form of some safety check in debug mode it would be even better, but I don’t know in which cases that is possible / planned.

Ok i get you now …

This is nice to have in Debug builds, but it only works for pointers.
Like @Sze suggests, the way for example Valgrind implements it is way better, since its not limited only to pointers. It works overall on uniitiated memory, which is what undefined is.

That kind of check would be nice to get to Debug / RelseaseSafe builds. Similar to checks for returning pointer to a droped stack value, etc.

I heard some more of these checks are planned to be attempted. But no idea on the timetable for that.

Valgrind is great and all, but it’s bringing an elephant gun to a mouse fight. Ever tried to build Valgrind? What about on Windows? I don’t think ā€œValgrind fixes thisā€ is a good argument against a feature like this.

Correct, it only works in Debug builds, where you try to have lots of assertions to catch errors, and it only works for pointers, the singular type in Zig which can create illegal memory access if it happens to be undefined.

No one, I hope, is under the misapprehension that assertions are a complete and infallible cure for all which ails a program. But I do not understand the argument which seems to be forming up for not enabling assertions of this kind to be reliable.

It seems like a step is getting skipped: I’m hearing ā€œthis is ad-hoc and nonportable, let’s not do itā€, when the idea is to make it rigorously defined and portable.

1 Like

I am not strictly against it, I would just prefer it (if possible), if the use-cases where this would be used, were already covered by more automatic checks. (currently they aren’t, but will they be through new safety checks?)

If all ways to use this tool, already had a safety check that detects it sooner, than there would be no point to add this tool which needs to be used manually.

There was an issue about making valid pointer addresses strictly known, if that was implemented we also could use that, but if I remember correctly that issue also talked about using those invalid values to track information used for safety-checks, so I think it would make this idea obsolete.

I guess I am hesitant on going for a solution that requires a lot of manual work, when we could possibly get one that requires no work (from language users).

1 Like

I think there’s going to be a limit past which this won’t work. In a way it’s more documentation than anything, debug modes will definitely panic if the pointer is referenced, but I’d rather have a specific assertion fail.

There’s a reason I made the example a threadlocal var, to emphasize that the example represents ā€˜global’ state. One of the reasons that should be used sparingly is that it very quickly makes formal analysis intractable. Or even informal analysis. So if the logic isn’t completely trivial, that leaves assertions, and if the logic is trivial, well, it doesn’t have to stay that way.

It’s not an especially heavyweight mechanism we’re talking about here, it’s just a builtin advertising something important about the build configuration.

Probably clearer to call it builtin.undefined_ptr_value, which is either null or, presumably, a bunch of 0xaa.

Usage like this would be pretty simple:

fn ptr_ok(ptr: usize) void {
    if (builtin.undefined_ptr_value) |invalid|
        std.debug.assert(ptr != invalid);
}

You convinced me enough to the point where I am in favor of just having it as an experimental feature, so that people could find out, how it works out in practice and from that experience, decide whether to keep it.

1 Like

AFAIK Valgrind is not available on Windows, so things that work on all our targets are useful.

Possibly relevant issues:

2 Likes