Error on object in object loops

In that case, yeah I guess I don’t see downsides to doing it. It’s probably not going to be noticeably slower. Though you should measure that if it’s a concern.

But as Fredrik said, if you want safety, don’t think about looking for individual faults. Just have a supervisor process that stops the engine if it becomes unresponsive.

Reporting individual faults is more about feedback to authors or testers than safety.

The library only runs one instruction at a time, so the front-end can effectively police the code without the need of a separate process as long as the library can guarantee a single instruction will always terminate. This scales far better than a separate watchdog process.

Hang on, is that true? The canonical @remove_obj implementation doesn’t run up the parent chain. It gets C’s parent (which is A) and then runs along A’s child/sibling chain.

You could hang the instruction if the sibling chain contains a loop. But I don’t think you can cause that situation with Z-machine instructions.

EDIT: I guess the other instruction-breaking case is if C’s parent is A, but C is not the child or sibling of any object. But again, you can’t get there by issuing instructions. You’d have to construct a non-well-founded game file.

1 Like

That’s correct. Removing an object must run through the child and its sibling chain. If that chain contains a loop and does not contain the object requested to remove, then the result is an instruction that will not terminate. I was not precise when explaining it here, but that is indeed what my code checks for.

Edit: Also me not being clear - my original question is about the parent chain, not the sibling chain. That check is not needed for safety, just informational. Checking for loops in the sibling chain is absolutely needed for my usage and I’ve already implemented it, so it wasn’t part of my question. Sorry for the confusion.

That is correct. But in order for my library to guarantee all instructions terminate when run against arbitrary (and possibly malicious) files, it is necessary.

Another edit: Originally I had an example here where I thought you could acheive this starting from a well-founded tree and using only insert_obj and remove_obj, but it was not a good example. Still you can achieve it via instructions even with a well-founded tree at start through direct memory manipulation.

Thanks everyone for all the feedback and discussion, despite my rather chaotic descriptions. I really enjoy working on the library, but lately I’ve been bogged down working on the frontends, which I find less interesting.

To summarize:
I have implemented sibling chain loop breaking for remove_obj because it is necessary to guarantee that instruction will terminate when run against arbitrary code. The library notifies the frontend when this happens.

I was debating on adding loop detection in the chain of parents/grandparents for insert_obj. This is not needed for safety but I thought might be useful information. Walking that chain is a small performance hit because it isn’t strictly needed to complete the instruction.

I’m still not certain I’ll add it. I make an effort to have the library indicate whenever the running of the story file violates the standard in any way or where some information might be useful, but I’m also careful to not add runtime overhead where it isn’t needed - even if the actual performance isn’t a problem. I keep going back and forth on it.

1 Like

Yes, that’s true.

Still you can achieve it via instructions even with a well-founded tree at start through direct memory manipulation.

Also true. (That is, change values in the object tree by writing to that section of memory.)

(I can’t remember offhand if this is strictly legal. Is the interpreter allowed to “lift” the object tree into native code and ignore direct writes? I suspect not, looking at the comment in 12.4. Anyhow in practice nobody does this.)

Well, I’m glad you got enough information to make a decision!

1 Like

For the curious, here’s a (to me) very amusing example of what might be done with malicious z-code on a server:

Consider a story file consisting of only four instructions:
Read a character of input (with a small 0.10 second timeout and a timeout routine).
Jump back to start.

The code can repeat endlessly despite watchdog processes checking to see if the zcode “stops to allow input”. The really fun bit is the timeout routine, which is just a print_addr and a return. Let’s say the string printed is 128K consisting of nothing but the same abbreviation repeated over and over. The abbreviation itself is a 256K string (no nested abbreviations - they’re illegal!). The resulting amplification gives you something like 20GB of output repeated at 0.10 second intervals and fits in a perfectly valid Z8 story file. Fun stuff. :upside_down_face:

Another variation would move the print_addr below the read_char and make the timeout just return true to end the read_char.

I ended up not adding the check for insert_obj.
It might be mildly useful to know during game development when “move red box to blue box” followed by “move blue box to red box” happens, but it doesn’t present the safety issue of a non-terminating instruction, doesn’t result in an unrecoverable state from the z-machine’s perspective (though it may from the game’s), and it requires extra work to walk through the parents. Three strikes and yer out. :baseball:

1 Like