delphimemory-corruptionfastmmtxmldocument

TXMLDocument.Active := False causes FastMM4 errormessage "FastMM detected that a block has been modified after being freed"


we have a strange effect using Delphi XE8 with FastMM4 (Version 4.992) in FullDebugMode.

To reproduce the effect, just create a new TForm application, put FastMM4 in the first line of the DPR file, put a Button on the Form and put the following code in the clickhandler:

(You need to have FastMM 4 installed, FullDebugMode must be enabled in the FastMM4Options.inc file and the FullDebugMode.dll has to be in the output folder of your programm!)

procedure TForm3.Button4Click(Sender: TObject);
var
    dm: tdatamodule;
    doc: txmldocument;
begin
    //this causes the messagebox to be shown directly after clicking the button. 
    //without it the box is shown when the program is exited.
    FastMM4.FullDebugModeScanMemoryPoolBeforeEveryOperation := true;        

    //prepare a xml document
    dm := tdatamodule.create(nil);
    doc := txmldocument.create(dm);
    doc.LoadFromXml('<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?><root/>');

    //doc.Active := true;   //no need to set active to true. doc is already active at this point!

    //the following does matter. ChildNodes needs to be accessed twice!
    //doc.DocumentElement.ChildNodes.FindNode('first');
    //doc.DocumentElement.ChildNodes.FindNode('second');

    //alternative access:
    doc.DocumentElement.ChildNodes.count;                                       //first access to ChildNodes
    doc.DocumentElement.ChildNodes.count;                                       //second access to ChildNodes

    //this does matter. if doc stays active then there is no problem!
    doc.Active := false;                                                        //active needs to be set to false!

    //doc.free; //doesn't matter if doc is freed manually. doc will be freed when datamodule is freed. problem occurs either way.
    dm.free;
end;

When you click the button, FastMM4 will show a large messagebox with the following report:

FastMM has detected an error during a free block scan operation. FastMM detected that a block has been modified after being freed. 

Modified byte offsets (and lengths): 80(1)

The previous block size was: 228

This block was previously allocated by thread 0xC74, and the stack trace (return addresses) at the time was:
406C46 [System.pas][System][@GetMem$qqri][4565]
407A73 [System.pas][System][TObject.NewInstance$qqrv][15975]
5DF25D [Xml.XMLDoc.pas][Xml.XMLDoc][Xmldoc.TXMLDocument.NewInstance$qqrv][2368]
408202 [System.pas][System][@ClassCreate$qqrpvzc][17290]
5DF116 [Xml.XMLDoc.pas][Xml.XMLDoc][Xmldoc.TXMLDocument.$bctr$qqrp25System.Classes.TComponent][2344]
5E2094 [Unit3.pas][Unit3][TForm3.Button4Click$qqrp14System.TObject][47]
407E6F [System.pas][System][@IsClass$qqrxp14System.TObjectp17System.TMetaClass][16465]
51EB39 [Vcl.Controls.pas][Vcl.Controls][Controls.TControl.Click$qqrv][7361]
535CF3 [Vcl.StdCtrls.pas][Vcl.StdCtrls][Stdctrls.TCustomButton.Click$qqrv][5327]
536801 [Vcl.StdCtrls.pas][Vcl.StdCtrls][Stdctrls.TCustomButton.CNCommand$qqrr26Winapi.Messages.TWMCommand][5788]
51E5C8 [Vcl.Controls.pas][Vcl.Controls][Controls.TControl.WndProc$qqrr24Winapi.Messages.TMessage][7245]

The block was previously used for an object of class: TXMLDocument

The allocation number was: 650

The block was previously freed by thread 0xC74, and the stack trace (return addresses) at the time was:
406C62 [System.pas][System][@FreeMem$qqrpv][4613]
407A91 [System.pas][System][TObject.FreeInstance$qqrv][15984]
40824D [System.pas][System][@ClassDestroy$qqrxp14System.TObject][17333]
5DF241 [Xml.XMLDoc.pas][Xml.XMLDoc][Xmldoc.TXMLDocument.$bdtr$qqrv][2363]
4C3661 [System.Classes.pas][System.Classes][Classes.TComponent.DestroyComponents$qqrv][15648]
4C3100 [System.Classes.pas][System.Classes][Classes.TComponent.$bdtr$qqrv][15445]
4C5543 [System.Classes.pas][System.Classes][Classes.TDataModule.$bdtr$qqrv][16694]
407B97 [System.pas][System][TObject.Free$qqrv][16052]
5E20F2 [Unit3.pas][Unit3][TForm3.Button4Click$qqrp14System.TObject][63]
51EB39 [Vcl.Controls.pas][Vcl.Controls][Controls.TControl.Click$qqrv][7361]
535CF3 [Vcl.StdCtrls.pas][Vcl.StdCtrls][Stdctrls.TCustomButton.Click$qqrv][5327]

The current thread ID is 0xC74, and the stack trace (return addresses) leading to this error is:
416526 [fastmm4.pas][FastMM4][InternalScanMemoryPool$qqruiui][10018]
416601 [fastmm4.pas][FastMM4][ScanMemoryPoolForCorruptions$qqrv][10092]
4161DF [fastmm4.pas][FastMM4][DebugFreeMem$qqrpv][9761]
406C62 [System.pas][System][@FreeMem$qqrpv][4613]
407A91 [System.pas][System][TObject.FreeInstance$qqrv][15984]
40824D [System.pas][System][@ClassDestroy$qqrxp14System.TObject][17333]
407B8A [System.pas][System][TObject.$bdtr$qqrv][16044]
40D5DD [System.pas][System][TInterfacedObject._Release$qqsv][37311]
40D4B7 [System.pas][System][@IntfClear$qqrr44System.%DelphiInterface$17System.IInterface%][36327]
40B189 [System.pas][System][@FinalizeArray$qqrpvt1ui][31704]
40B079 [System.pas][System][@FinalizeRecord$qqrpvt1][31407]

Current memory dump of 256 bytes starting at pointer address 7EA18B60:
98 72 5F 00 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 7F 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 BD 4A 52 7A 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 00 00 00 00 00 00 00 00
˜  r  _  .  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €    €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  ½  J  R  z  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  .  .  .  .  .  .  .  .

If we interpret this correctly, then FastMM is telling us, that the Memory of the TXMLDocument Object has been modified, after it was freed.

"Some Code" seems to have written the $7F in the middle of all these $80 in the memoryblock of the TXMLDocument that has already been freed.

This does happen only if ChildNodes is accessed twice (!) and if the Active property of the TXMLDocument is set to false before the Object is freed!

Questions:

Can someone explain what is going on here?

Additional observation:

If the TXMLDocument is freed without active being set to false, then there is no problem.

If we look at the destructor of TXMLDocument we see that there is some additional code before active is set to false there:

destructor TXMLDocument.Destroy;
begin
  Destroying;
  if FOwnerIsComponent and Active and Assigned(FDocumentNode) and (FRefCount > 1) then      //additional code
    (FDocumentNode as IXMLNodeAccess).ClearDocumentRef;                                     //additional code
  SetActive(False);
  FreeAndNil(FXMLStrings);
  inherited;
end;

Now, if we modify our own examplecode and call

(FDocumentNode as IXMLNodeAccess).ClearDocumentRef; 

before setting active to false, then the problem is gone!

Code would look something like this:

type
  TMyXMLDocument = class(TXMLDocument);

procedure TForm3.Button4Click(Sender: TObject);
var
    dm: tdatamodule;
    doc: txmldocument;
begin
    FastMM4.FullDebugModeScanMemoryPoolBeforeEveryOperation := true;

    dm := tdatamodule.create(nil);
    doc := txmldocument.create(dm);
    doc.LoadFromXml('<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?><root/>');

    doc.DocumentElement.ChildNodes.count;
    doc.DocumentElement.ChildNodes.count;

    (TMyXMLDocument(doc).GetDocumentElement as IXMLNodeAccess).ClearDocumentRef;  //<-- with this  additional hack the problem is gone!

    doc.Active := false;                                                        //no more problem!

    dm.free;
end;

Solution

  • When calling methods/functions that return interfaces, the compiler implicitly generates variables to put these results into. They live until the end of the method and are then being finalized/cleared.

    In your case doc.DocumentElement.ChildNodes does 2 method calls that return interfaces. Now when you destroy the TXMLDocument instance these implicit variables still point to some memory and due to the _Release call being made when the compiler generated code calls IntfClear they call into some method that has no object anymore - FastMM is able to track and report that.

    So this mentioned call is translated into this:

    var
      ...
      nodes1: IXMLNodeList;
      node1: IXMLNode;
      nodes2: IXMLNodeList;
      node2: IXMLNode;
    begin
      ...
    
      node1 := doc.DocumentElement;
      nodes1 := node1.ChildNodes;
      nodes1.count;
      node2 := doc.DocumentElement;
      nodes2 := node2.ChildNodes;
      nodes2.count;
    
      dm.free;
    
      nodes1 := nil;
      node1 := nil;
      nodes2 := nil;
      node2 := nil; // <- boom
    

    In many cases this error does not manifest without using FastMM unless the previously deallocated memory was not being reused for another allocation which then results in weird AVs.

    Rule of thumb: do not destroy instances that are referenced by some interface in the same scope as this might lead to implicitly created interface variables still pointing to them.