Error on object in object loops

With respect to the Z-machine:

Would it be ok for an interpreter to characterize inserting an object into a second object, as a (presumably) non-fatal error when the prospective parent is already a child, grandchild, etc. of the first?

I know this is usually unintentional and a game bug, e.g. give axe to troll, but has it ever been used intentionally?

The z-machine standard says it is the game’s responsibility to keep the object tree well founded, but nothing more. Obviously there would be a non-zero performance penalty for an interpreter to walk the tree and detect this condition.

1 Like

Sounds perfectly fine, and a pretty good idea.

On an interpreter that is meant to run on modern hardware, I wouldn’t worry too much about the performance hit. It shouldn’t cost a lot.

Interpreters do not, in general, check for well-foundedness at all. It’s not an error at all. If you try to move an object into its descendant, the interpreter just does it.

(The I6 veneer code checks for this in strict mode, however.)

1 Like

I realize interpreters as a rule don’t check this. I was just thinking it might be useful for my z-machine library to report when this happens (which is moot of course if I never get around to releasing it publicly). But if having a malformed object tree is definitely not an error, then I’d hesitate to include it.

1 Like

Well, other libraries (I suspect all other Z-code libraries) assume the object tree stays well-founded. That is, if there’s a loop, your first hint is when a library routine hangs trying to iterate up the object tree.

If your library also makes that assumption, then it makes sense to check for loops.

If you want to write a library that doesn’t make that assumption, and can operate safely with loops in the object tree, you’re clear to do that. But you’ll have to warn Inform users to turn off strict mode, or they won’t be able to get into those states.

1 Like

Ah, I think I may have used the wrong terminology. I don’t mean a z-code library for Inform or anything like that. It’s essentially an interpreter core written as a reusable library rather than a monolithic application. It is able able to report common z-machine issues/errors to whatever interpreter frontend is using it. The front-end application is free to ignore, log, or halt on any of them while the library is completely agnostic on the severity of any given issue.

2 Likes

Personally, I think that moving things/objects inside trees should be allowable, e.g. moving from a pocket to other, not necessarily in the PC object, ex. the classical weight scale puzzle.

Best regards from Italy,
dott. Piergiorgio.

1 Like

Of course it’s allowed to move something within a tree. But you shouldn’t move an object so an infinite loop is created, e.g. your coat has a pocket and there’s a plastic bag in the pocket, and you decide to put the coat in the plastic bag. Now if you follow the parent relationship from the bag and up, you get:

bag → pocket → coat → bag → pocket → coat → bag etc forever

This is what’s discussed here.

You could lock up some Infocom games with a bug like this, IIRC.

2 Likes

That should be a restriction of the game/library though, not the VM. I could imagine some kind of surrealist game where the object tree has a containment loop to navigate, which should be fine as long as the library is customised to both allow for it to be constructed, and to detect such loops and break out of processing them rather than recursing forever.

3 Likes

Ok, sorry. I should have understood what you mean from your earlier posts.

It’s a bit of a grey area. Everybody assumes that this won’t happen, but there’s no part of the system which is responsible for preventing it. (Strict mode is generally off in release mode, and I don’t think Zilf has an equivalent.)

Really, if I were writing this game, I would not model it in the object tree for exactly this reason. Some error-checking interpreter might block it, and why take the risk that the game won’t run for somebody? (Instead, I’d add a custom property called zparent and adjust the library to use that.)

3 Likes

That is the sort of situation that made me hesitate to implement this.

After Zarf’s post I feel a bit more confident that signaling this from the core to the interpreter front-end could be useful for authors to detect bugs. My assumption is that any front-end would be configurable - error on everything (for authors and testers) and ignore most errors (for players).

My library already deals with this situation when removing an object. Removal is more critical as the tree must be recursed and failing to break loops can result in a single z-machine instruction looping forever. One of my design goals is being safe to run untrusted z-code on a server. There are a number of things that are not strictly z-machine errors that make this problematic, even as sandboxed as the z-machine is. This is one of them.

1 Like

It’s easy to make a mistake in Z-code which will lock up the game, using 100% of the available CPU forever, i.e. any non-terminating loop.

For your usage scenario, I’d limit the stack size and put a watchdog on execution time between checkpoints (when the game requests user interaction), plus if the user hasn’t actually interacted with the game in n seconds, the interpreter pauses until the user does something (e.g. action oriented games can check for user input all the time, but continue to run even if no input is received).

2 Likes

There are many more that can be done with malicious hand-crafted z-code.

Yes those are basic steps, but there are other issues such as object loops that need special handling as they can cause loss of control within a single instruction.

1 Like

If you have a watchdog outside the process that runs the interpreter, it doesn’t matter if it’s a Z-code loop or a single instruction that locks up. If the process fails to check in at requested intervals, it’s shut down.

2 Likes

True, but having the core safeguard things at the instruction level makes it much simpler for the interpreter to watchdog the zcode itself, obviating the need for a separate process.

1 Like

FWIW, if object A has parent object B and object B has parent object A, this doesn’t create an infinite loop within a single instruction. Both objects have valid parents which can be returned. Even performing the move to get this position shouldn’t be problematic. The problem arises if a routine written in Z-code tries to follow the object tree up or down to the end.

True, the creation of a loop in itself is not a problem for safe z-code execution in most circumstances. I felt that indicating when one is created could be helpful for authors and testers.

But loops present a serious problem for safe z-code execution in general. Consider this:

Object A has parent B and object B has parent A. Now issue an instruction to remove Object C from A. The result is an infinite loop within a single instruction.

Edit: That was a bad example. A single instruction infinite loop does not occur this way and can only happen if there is a loop in the sibling chain. A loop in the chain of parents does not cause this problem but it may still be helpful to know when it occurs.

You’re proposing to deliberately not follow the spec, but for what benefit? I don’t think you’ve given any reason substantial enough to justify disregarding this part of the spec.

Also keep in mind that it’s possible some game might temporarily put the object tree into a loop before immediately fixing it. You’d make that fail when it isn’t any kind of invalid action.

Edit: I missed that you said a non-fatal error in the OP. So you mean a warning? That could still produce a lot of unnecessary noise. Maybe try it in a log channel and see if games actually cause it?

1 Like

I disagree that I would not be following the standard.

The relevent section says only that the interpreter is not required to check, not that it must not check. And again I am only suggesting an indicator to the frontend that a loop has been formed, the front-end decides the severity. It is free to ignore. And as I pointed out above, loops in the parent chain and sibling chain present a real issue for running untrusted z-code in a server environment. Bullet point (c) below definitely precludes loops from being valid state.

12.5

It is the game’s responsibility to keep the object tree well-founded: the interpreter is not required to check. “Well-founded” means the following:

(a) An object with a sibling also has a parent.

(b) An object is the parent of exactly those objects in the sibling list of its child.

(c) Each object can be given a level n, such that parentless objects have level 0 and all children of a level n object have level n+1.

From my library’s point of view, it generates a ‘fault’ event that is sent to the front-end with information about the type and details of the fault. The front-end decides whether it is a warning, an error, or unimportant and what to do about it: i.e. to display, log, or ignore. My library is completely error agnostic: treating stack overflows, illegal variable reads, bad output characters, etc. all equally.

Edit: I am simultaneously developing a couple front-ends for my library. My approach has been to allow virtually anything to run, but allow a configurable level of warnings. There are pretty much no fatal errors.