Circular references in sObjects causes StackOverflowError
Last updated 2019-09-07 ·Reference W-2511387 ·Reported By 13 users
If there is a circular reference in sObjects, an operation calling the equals() method or the hashCode() method to the sObject records results in a StackOverflowError.
The equals() method or the hashCode() method can be called when putting the sObject record to a Set/Map or if turning Apex Code Log Level into FINEST.
1) Create custom objects, say CircularRefObj1, CircularRefObj2.
2) On CircularRefObj1, create a lookup relationship field "obj2ref" which is related to CircularRefObj2.
3) On CircularRefObj2, create a lookup relationship field "obj1ref" which is related to CircularRefObj1.
4) Run the following apex code via workbench/dev console.
CircularRefObj1__c o1 = new CircularRefObj1__c();
CircularRefObj2__c o2 = new CircularRefObj2__c();
o1.obj2ref__r = o2;
o2.obj1ref__r = o1;
Set<sObject> s = new Set<sObject>();
s.add(o1); // call SObjectScriptRow.hashCode().
s.add(o1); // call SObjectScriptRow.hashCode() and equals().
You will see that the execution fails with the System.LimitException, as follows.
EXCEPTION: System.LimitException: Maximum stack depth reached: 1
STACKTRACE: AnonymousBlock: line 11, column 1
LINE: 11 COLUMN: 1
If you set the Apex Code Log Level to FINEST, you will see that the SystemLimitException occurs at line#8.
An sObject is represented as an SObjectScriptRow object in apex, and its field values are stored in a HashMap in a MapBasedRowProvider the SObjectScriptRow object has. When the sObject has a relationship field to another sObject, the HashMap in the MapBasedRowProvider holds a SObjectScriptRow object of the sObject to which the relationship field refers.
If the hashCode() method is called to the SObjectScriptRow object, it calculates a hash value by calling the hashCode() method to the HashMap in the MapBasedRowProvider.
And the HashMap calls the hashCode() method to each value. It eventually calls the hashCode() method to the SObjectScriptRow object of the related sObject.
In this way, if there is a circular reference in sObjects, they recursively call the hashCode() method each other, and consume stack memory.
The same is true in the equals() method.
Is it Fixed?
Any unreleased services, features, statuses, or dates referenced in this or other public statements are not currently available and may not be delivered on time or at all. Customers who purchase our services should make their purchase decisions based upon features that are currently available.