Jason started work on a C++ application doing quantitative work. The nature of the program involves allocating all sorts of blocks of memory, doing loads of complicated math, and then freeing them. Which means, there's code which looks like this:

for( i = 0; i < 6; i++ )
{
    if( h->quant4_bias[i] )
        free( h->quant4_bias[i] );
}

This isn't terribly unusual code. I have quibbles- why the magic number 6, I'd prefer the comparison against nullptr to be explicit- but this isn't the kind of code that's going to leave anybody scratching their head. If h->quant4_bias[i] is pointing to actual memory, free it.

But this is how that array is declared:

uint16_t        (*quant4_bias[4])[16];

Uh… the array has four elements in it. We free six elements. And shockingly, this doesn't crash. Why not? Well… it's because we get lucky. Here's that array declaration with a bit more context:

uint16_t        (*quant4_bias[4])[16];
uint16_t        (*quant8_bias[2])[64];

We iterate past the end of quant4_bias, but thankfully, the compiler has put quant8_bias at the next offset, and has decided to just let the [] operator just access that memory. There's no guarantee about this- this is peak undefined behavior. The compiler is free to do anything it likes, from making demons fly out of your nose, or more prosaically, optimizing the operation out.

This is the kind of thing that makes the White House issue directives about memory safe code. The absence of memory safety is a gateway to all sorts of WTFs. This one, here, is a pretty mild one, as memory bugs go.

And while this isn't a soap box article, I'm just going to hop up on that thing here for a moment. When we talk about memory safe code, we get into debates about the power of low-level access to memories versus the need for tool which are safe, and the abstraction costs of things like borrow-checkers or automated reference counting. This is a design challenge for any tool. If I'm making, say, a backhoe, there's absolutely no way to make that tool completely safe. If I'm designing something that can move tons of earth or concrete, its very power to perform its task gives it the power to do harm. We address this through multiple factors. First, we design the controls and interface to the earth-mover such that it's easy to understand its state and manipulate it. The backhoe responds to user inputs in clear, predictable, ways. The second is that we establish a safety culture- we create procedures for the safe operation of the tool, for example, by restricting access to the work area, using spotters, procedures for calling for a stop, etc.

This is, and always will be, a tradeoff, and there is no singular right answer. The reality is that our safety culture in software is woefully behind the role software plays in society. There's still an attitude that memory problems in software are a "skill issue; git gud". But that runs counter to a safety culture. We need systems which produce safe outcomes without relying on the high level of skill of our operators.

Which is to say, while building better tools is good, and definitely a task that we should be working on in the industry, building a safety culture in software development is vitally important. Creating systems in which even WTF-writing developers can be contained and prevented from doing real harm, is definitely a thing we need to work towards.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!