chapel-lang / chapel

a Productive Parallel Programming Language
https://chapel-lang.org
Other
1.78k stars 418 forks source link

Printing classes with cycles leads to infinite recursion and bus error #20710

Open mstrout opened 1 year ago

mstrout commented 1 year ago

Summary of Problem

When I create a reference cycle between two class instances and then cast one of those instances to a string, a bus error occurs.

Better behavior would be to detect the infinite loop and possibly throw an exception or print an error?

Steps to Reproduce

Source Code:

class Node {
 var val : string;
 var other : shared Node?;
}
var n1 = new shared Node("node 1",nil);
var n2 = new shared Node("node 2",n1);
n1.other = n2;
writeln(n1:string); // THIS causes a bus error before printing anything out.

Compile command: chpl foo.chpl

Execution command: ./foo

Configuration Information

I am using the homebrew install of 1.28.0.

bradcray commented 1 year ago

Better behavior would be to detect the infinite loop

Yeah, this is a long-term known-but-ignored issue we have when printing classes (which is essentially converting them to strings in another way): We just recursively print their fields, so will recurse infinitely for anything that's not a DAG. We've discussed detecting the cycles (though that can get expensive for a large graph, and essentially equivalent to pickling or pointer swizzling, I think?) or just making the default class printer less likely to print recursively (though it's pretty cool to have it print trees automatically; DAGs are arguably a little redundant the way they're printed).

A user-level workaround is to add your own writeThis() method to the class to print it the way you want (presumably cycle-free).

vasslitvinov commented 1 year ago

I like Brad's proposal to detect statically where the recursion could occur, https://github.com/chapel-lang/chapel/issues/21450#issuecomment-1410868751 .

Note that recursion could potentially go through a record and/or multiple intermediaries.

Another idea is to add an argument to writeThis that's one of:

The predefined writeThis, then, invokes the specific writeThis with the initial value of the extra argument, which is 3, say, for recursion depth or the empty set for the set.