The Z machine creates a pretty standard 16 bit address space and folks have already done things like implement general purpose memory allocators within the dynamic memory.
Array overruns are a common source of programming bugs - the standard even mentions things like the read opcode should abort if requested to read too few characters or write out too few parse items since that’s likely a memory bug.
Off the top of my head, any interpreter running on a modern desktop could implement some pretty simple but robust memory checking as follows:
- Allocate a shadow array of 64k uint16_t’s.
- Each entry of the shadow array contains the size of the remaining space in that segment
What’s a segment? Well, the header is a segment of 64 bytes at address zero. The dictionary segment contains all of the dictionary words. Every global array (text buffer, parse buffer, etc) would be its own segment.
Any memory location that we’d expect to never be accessed by a loadb or loadw would have a zero stored in the shadow array.
For best results, you’d want to parse Inform-style debug information to glean the locations of as many valid segments as possible. Some can be obtained strictly from the header though.
This isn’t foolproof, of course, since there’s nothing preventing you from manually computing a completely bogus address, but it would catch a lot of cases where we were attempting to read past the end of a known data structure.
But this way, any loadb, loadw, storebor storew could easily bounds-check its base address and count to detect reading or writing outside of the intended area.
Somewhat related, is there a standard way to determine the highest-numbered global variable, for systems that don’t unconditionally reserve all 240 slots? It could be computed at runtime by either looking for the opcode with the largest variable index reference, or possibly by looking for any pointer “closer than” 480 bytes from the declared start of global variables. The main reason for trying to determine the number of globals would be to detect attempts to inc/dec something that contained an invalid index. (Well, a value between 1-15 should also be range checked against the number of variables in the current routine too).
(I realize “making Z-machine games memory safe” is an extremely niche subject)
-Dave