The headline sets the stage, and the article delivers.
This was the most interesting bug I’ve encountered for a while. I initially had a hard time believing that a bug like this would directly tie to a specific OS release, but I was proven completely wrong. At the end of the day, it was a simple bug in San Andreas and this function should have never worked right, and yet, at least on PC it hid itself for two decades.
This is an interesting lesson in compatibility: even changes to the stack layout of the internal implementations can have compatibility implications if an application is bugged and unintentionally relies on a specific behavior. This is also not the first time I encountered issues like this: regular visitors might remember Bully: Scholarship Edition which famously broke on Windows 10, for very similar reasons. Just like in this case, Bully should have never worked properly to begin with, but instead, it got away with making incorrect assumptions for years, before changes in Windows 10 finally made it run out of luck.
↫ Adrian Zdanowicz
Incredible story.
It’s a fun little write up for those interested in debugging sessions, haha.
This should have been caught (*). Programming errors in C often result in “undefined behavior” and by sheer luck earlier values on the stack happened to work. A windows update would clobber “unused” stack values. It’s clearly a GTA bug, but I’d say it’s also partly the fault of unsafe languages for enabling these kinds of memory faults in production software. I suspect huge swaths of software out there have latent faults like this but the triggering conditions are rare enough that developers don’t take notice.
Edit: The article does state that this error was caught on the xbox version, but the original PC version remained broken.
But C/C++ are fast (whatever that means) and code infested with hidden bugs and Undefined Behaviour is executing quickly. That’s all that matters, isn’t it?
Recent C++26 spec introduces even more validity of something that every compiler for memory safe languages would smack the developer in the face (e.g. 2 uninitialized variables are equal), but look how fast are they and they crash quickly.
Fossils in commitees for C/C++ actively block any attempts to modernize the language to introduce safety, but look how shiny C/C++ are. Did I mention they are fast and how performant integer and buffer overflows are?
“Fossils in commitees for C/C++ actively block any attempts to modernize the language” : considering that the C/C++ has already been quite bastardized/compromised with concepts “borrowed” from other languages, which now make C/C++ a Frankenstein’s coding language with so many tricks and tips it is no more sane to code using it.
Instead start anew from a blank page by using Jancy and/or Pony.
https://vovkos.github.io/jancy/language/intro.html
https://www.ponylang.io/discover/what-makes-pony-different/
Or code in Pascal (FreePascal’s compiler is damn good) :
https://www.youtube.com/@silvercoder70
Blaming the programming language and not the programmer. “A bad workman always blames his tools”
Memory unsafe languages are not inherently a problem, but yes, the programmer does need to make sure that they’re not writing sloppy code. Memory safe languages are indeed a great improvement (abstracting away that “boring” memory management stuff) and much more convenient for programmers. But sadly these are somewhat of a modern luxury, and were not as prevalent in the days of GTA SA
Then why people are driving more and more “secure” vehicles instead of using old fashioned cars/bikes with little to none “patronizing” features ?
They just have to learn how to drive.
I see a lot of farmers sticking with older tractors which are easier to fix/modify by the farmers themselves, rather than deal with the modern “conveniences”.
I’m not saying that older languages are better. Far from it. But they do have their place, and can be operated safely. Much like your analogy, a 57 Bel Air is perfectly safe when operated correctly, but shit goes downhill if something bad happens. Same goes with memory unsafe languages. And again, they’ll be people who prefer driving that 57 bel air, and prefer coding in C. And then they’ll be cases where, like the farmers driving old tractors, programming in C might even be more beneficial and convenient than a memory safe language like Rust.
The123king,
The tractor analogy is flawed. A tractor that can be fixed/modified by farmers themselves is not a logical equivalent to unsafe language but rather FOSS. I don’t want to get into the weeds with analogies, but a better logical equivalent to safe vs unsafe languages would be farmers having safe & reliable versus unsafe & unreliable equipment.
It’s easier said than done. After decades upon decades of faults with unsafe code, we ought to agree that human developers operating under realistic project management conditions don’t produce faultless code for projects whose scope grows beyond academic examples. In the past unsafe languages were used out of necessity. As time rolls on though, the continued use of unsafe languages becomes less about technical necessity and performance and more about habits and conventions.
Take the debate between metric and imperial units. I absolutely hate going into a US hardware store to size up dimensions in terms of feet plus fractions of inches, AWG, etc. “The calipers show 0.39in. 3/8 is a tad too small. 7/16 is too big, 13/32 is too big…..ah yes 25/64, here we go.!”. If we could only get over the bridge and embrace metric for everything, nobody would actually want to go back to imperial, however the problem is getting over the bridge. It’s the same with software developers. We are creatures of habit and we resist change not because it is bad but because it is hard.
Oooh the right to repair argument, that rings strongly with me. John Deere is the absolute worst company in the entire world as a farmer to have to work with (with perhaps the exception of nationalized farming companies and monsanto) Everyone i know gets their new farming equipment elsewhere nowadays after John Deere introduced unfixable hardware without the proper software. Good lord if i owned any JD stock, i would sell it as fast as i could before it becomes worthless. True they make great harvesters when they work, but a software bug can ruin an entire farm.
Oh wait, it is going up, people are still buying it, just not in my circles.
The123king,
It’s not merely bad workmen blaming the tools though. The stats are clear when human coders are given unsafe tools, the incidents of software faults increases.
I agree that modern languages have improved, although to be honest the issue of undefined behaviors including the ones in this article were deficiencies with C/C++ long before GTA.
If the basis of your house is manure, it is unlikely to stay up for long. – Old viking saying about the anglo-saxons.
There is no such thing as “undefined behavior”, in the real world “undefined behavior” gets defined by the most popular implementation (which in this case is versions of Windows before Windows 11 24H2) and the less popular implementations get blamed by users for breaking compatibility.
Proper languages avoid such “undefined behavior” by either initializing uninitialized values to the zero of their type or by throwing an error when trying to use an uninitialized variable, but the god-forsaken language known as C reads whatever garbage exists in the allocated chunk of memory and enables “undefined behavior”.
kurkosdr,
I do appreciate your point about users blaming the OS, but at least as developers we should agree that when a stack frame is not initialized and depends on whatever was on the stack before it, that is undefined behavior. Sometimes it works and sometimes it breaks, but ultimately code that depends on undefined behavior is leaving things up to chance.
I agree. I’d also like to point out that there can be more sources of undefined behavior beyond uninitialized values.
Here’s an example that can surprise beginners…
https://learn.microsoft.com/en-us/cpp/c-language/bitwise-shift-operators
If you don’t respect the undefined constraints, you can see totally unexpected arithmetic errors. Technically undefined means we should not have an expectation of correctness.
The mathematically correct answer for shift>=32 is 0, however many systems just ignore the high bits and return technically wrong values leading to bugs that many developers don’t even realize can happen.
https://www.thecodingforums.com/threads/shifting-bits-shift-32-bits-on-32-bit-int.283343/
I still agree with your point, but the scope is bigger than just uninitialized values.
Thanks Thom, That was a great read, I do similar work in my job updating and maintaining 3D CAD code, some of which was written in the 1990’s. It was nice to see somebody else working thorugh one of these kinds of issues 🙂