Can running an abstract Pattern stack-overflowing sclang be considered a bug?

Somewhat of a pedantic question, but in my explorations I’ve discovered that

r = Pattern().asStream
r.next // stack overflows sclang "Interpreter has quit"

The default/abstract implementation of embedInStream in Pattern is such that it calls itself recursively in a tight loop, obviously expecting subclasses to implement some other behavior… Of course one answer could be “why you would want to run an abstract Pattern??”

1 Like
Pn(Pseries(0, 1, 0), inf).asStream.next  // kaboom

Pseq([Pseries(0, 1, 0)], inf).asStream.next  // kaboom

It is a bug, but a proper fix would require major surgery on the interpreter, and possibly interfere with legitimate long-running calculations.

So it’s probably never going to be fixed.

hjh

The problem comes from this mutual recursion in Pattern:

	asStream { ^Routine({ arg inval; this.embedInStream(inval) }) }
	embedInStream { arg inval;
		^this.asStream.embedInStream(inval);
	}

Looking at subclasses, it’s done like that so they can work by overriding at least one of those methods (which calls the other). So I think this is “by design–won’t fix”.

While there is a method for “subclass responsibility” (can’t remember exact name) it can’t be used for this “either-or” case.

Infinite recursion does explain the Pattern case, but not the Pn/Pseq cases I posted. Here, the infinite loop is because, when e.g. Pn calls embedInStream on its child, it expects the child to yield something. (If it does, this breaks the loop until a subsequent next call.) If the child doesn’t yield anything, then it just loops back, and there’s nothing to stop it.

Pretty sure nobody has ever tried it with Pattern until you did… but the other ones have been known for a long time.

hjh

1 Like

Yeah the subtlety is that there seems to be no way for a function to know if any of the functions it calls has called yield or not… There would have to be a separate signal from whatever Pn or Pseq emebeds that they’ve “yielded nothing since called”. Unfortunately putting a signal like that in all patterns is going to be quite a refactor.

A Routine level primitive, say hasYielded would be more useful perhaps. It would return a flag that is set on yield and atomically auto-reset on the call to hasYielded so that:

1.yield;
// more stuff
this.hasYielded; // -> true
this.hasYielded; // -> false

I suspect the (native) implementation would not be too hard since I’m guessing routine context switches “leave a trail” at the CPP-level implementation. It might be worth doing a RFC on this idea…