EAccessViolation
exception in his event handler's code.A quick look indicated that the
EAccessViolation
exception was thrown with the following message: "Access violation at 0x0108164d: read of address 0xdeadc30f
" and the following call stack:
ContosoInplaceContainer.TContosoCustomViewInfoItem.Destroy ContosoInplaceContainer.TContosoEditCellViewInfo.Destroy System.TObject.Free ContosoClasses.TContosoObjectList.FreeItem ContosoClasses.TContosoObjectList.Clear ContosoGrid.TContosoCustomRowViewInfo.ClearValuesInfo ContosoGrid.TContosoCustomMultiEditorRow.EditorsChanged ContosoGrid.TContosoEditorPropertiesCollection.Update System.Classes.TCollection.Changed System.Classes.TCollection.RemoveItem System.Classes.TCollectionItem.SetCollection System.Classes.TCollectionItem.Release System.Classes.TCollectionItem.Destroy ContosoGrid.TContosoCustomEditorRowProperties.Destroy System.TObject.Free System.Classes.TCollection.Delete Unit2.TForm2.DeleteEditor Unit2.TForm2.PropertiesEditValueChanged ContosoEdit.TContosoCustomEditingController.EditValueChanged ContosoInplaceContainer.TContosoEditingController.EditValueChanged ...The code in question is:
destructor TContosoCustomViewInfoItem.Destroy; begin // Crashes below: if (Control <> nil) and (Control.Controller.HotTrackController.HintElement = Self) then Control.Controller.HotTrackController.CancelHint; inherited Destroy; end;The "Contoso" refers to Contoso Ltd. - which is a fictional company used by Microsoft as an example company. Here it masks the real vendor of a certain well-known 3rd party library, as the point of this story is to show how EurekaLog can help you find bugs, not to ridicule any particular developer/vendor.
Unlike most other stories this one was extremely easy to resolve. Mostly because we had a reliable reproducible example.
First of all, take a closer look at the exception message: "...read of address 0xdeadc30f". Notice that the code is trying to access the DEADC30F address, which is close to the DEADBEEF address. The DEADBEEF is a special debugging marker (see the "When memory is released" option), indicating already released memory. In other words, this code is trying to access already deleted object.
The above means that our customer (or 3rd pary code, e.g. Contoso) have "use after free" bug somewhere. This is 100% reliable information, which can NOT be false-positive. In other words, if customer is sure that his code is correct - then he has found a bug in the Contoso library (congratulations!). And visa versa: if Contoso library code is correct, then there is a bug in customer's code on how he uses the Contoso library.
Armed with this information all there is left to do is to simply walk through customer's code, paying attention to all delete/free operations. Here is how it goes:
- The
TContosoCustomEditingController.EditValueChanged
fires an event handler, which is set to customer's code (PropertiesEditValueChanged
), which calls:Row1.Properties.Editors.Delete(1);
. - The
Delete
is a method of RTL'sTCollection
, which removes item from the collection and then deletes the item:
procedure TCollection.Delete(Index: Integer); begin Notify(TCollectionItem(FItems[Index]), cnDeleting); TCollectionItem(FItems[Index]).DisposeOf; // here end; destructor TContosoCustomEditorRowProperties.Destroy; begin FreeAndNil(FEditContainer); // IMPORTANT inherited Destroy; // here end;
Notice that theTContosoCustomEditorRowProperties.Destroy
is deleting theFEditContainer
field. However, there is a reference to that object in other place - as we will see below. - Now
TCollectionItem
's destructor will release itself from the owner (collection):
destructor TCollectionItem.Destroy; begin if FCollection <> nil then Release; // here inherited Destroy; end; procedure TCollectionItem.Release; begin SetCollection(nil); // here end; procedure TCollectionItem.SetCollection(Value: TCollection); begin if FCollection <> Value then begin if FCollection <> nil then FCollection.RemoveItem(Self); // here if Value <> nil then Value.InsertItem(Self); end; end; procedure TCollection.RemoveItem(Item: TCollectionItem); begin Notify(Item, cnExtracting); if Item = FItems.Last then FItems.Delete(FItems.Count - 1) else FItems.Remove(Item); Item.FCollection := nil; NotifyDesigner(Self, Item, opRemove); Changed; // here end;
- Removing item from the collection will run notifiers - including
TContosoEditorPropertiesCollection.Update
:
procedure TCollection.Changed; begin if FUpdateCount = 0 then Update(nil); // here end; procedure TContosoEditorPropertiesCollection.Update(Item: TCollectionItem); var I: Integer; begin for I := 0 to Count - 1 do GetItem(I).EditContainer.FCellIndex := I; Row.EditorsChanged; // here end;
- The Contoso library is trying to update editors:
procedure TContosoCustomMultiEditorRow.EditorsChanged; begin if Properties.Locked or VerticalGrid.IsLoading then Exit; ViewInfo.ClearValuesInfo; // here Changed; end; procedure TContosoCustomRowViewInfo.ClearValuesInfo; begin FIsRightToLeftConverted := False; FInitialized := False; ValuesInfo.Clear; // here ValuesLinesInfo.Clear; end; procedure TContosoObjectList.Clear; var I: Integer; begin if OwnObjects then begin for I := 0 to Count - 1 do // = 2 FreeItem(I); // here end; inherited Clear; end;
- There are 2 items in the
ValuesInfo
list. First one (index 0) is OK, second one (index 1) is what causing the issue:
destructor TContosoEditCellViewInfo.Destroy; begin if (EditContainer <> nil) and not EditContainer.IsDestroying then // here // ... end;
Here is the problem:EditContainer
is actually theOwner
, and it points to already deleted object - the one that was deleted on the step 2 inside theTContosoCustomEditorRowProperties.Destroy
.
- Customer's code deletes an item from the collection;
- The item deletes its field;
- The collection notifies about item's deletion;
- The notification callback tries to clear associated information and accesses the deleted field in process.
Are you asking how the code was working "flawlessly" before adding EurekaLog to the project? Well, simple: the deleted object remained unchanged in the memory, so the
TContosoEditCellViewInfo.Destroy
could successfully access the already deleted object and read unchanged data from it. Adding EurekaLog to the project with default settings (enabled memory checks) changes this behaviour by actually erasing deleted object/memory.So, is it a bug in the Contoso library? While it certainly does look that way, we can not be certain for sure, as we are not experts with that library. So it is also entirely possible that it is a customer's error.
Customer reported that this issue will be taken to the Contoso library support.
P.S. Read more stories like this one or read feedback from our customers.