When The Debugger Leaks…

… you may end up chasing memory leaks that don’t exist.

I was at work investigating some bizarre behavior of our application when dealing with a big customer file and I had ReportMemoryLeaksOnShutdown turned on as it was very likely there was some memory leaked.
So, I donned my LeakBuster suit, armed my LeakBlaster and started hunting the leaks, trying different workflows with different code-paths, putting some Breakpoints here or there with some Watches, and sure enough, I got a bunch of memory leaks reported upon closing the application. But those were somewhat bizarre, mainly UnicodeStrings with unhelpful stacktraces into the VCL like:
A memory block has been leaked. The size is: 36
This block was allocated ... and the stack trace (return addresses) at the time was:
4043E2 [System.pas][System][@GetMem][2979]
20036
The block is currently used for an object of class: Unknown

or
A memory block has been leaked. The size is: 20
This block was allocated ... and the stack trace (return addresses) at the time was:
404972 [System.pas][System][System.@GetMem][3693]
407B5B [System.pas][System][System.@NewUnicodeString][16751]
407D8C [System.pas][System][System.@UStrFromPWCharLen][17431]
...
407E63 [System.pas][System][System.InternalUStrFromPCharLen][17601]
The block is currently used for an object of class: UnicodeString

It took me some time to find a relatively simple workflow showing consistently a leak where I was pretty sure the code was OK and should not leak. Then I tried to run the application without debugging and it did not leak. And again within the debugger and it leaked again.
So, the debugger was leaking memory. I don’t remember ever seeing that before, except in the cases where calling some buggy code from the Evaluate dialog or in the Watches was causing a leak.

You can try yourself in D2010 or XE with this very simple application: Create New VCL Form Application, double click in the Form designer to create a FormCreate handler and replace the empty procedure by this code:

function Test(s: string): string;
begin
  Result := 'test: ' + s;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown := True;
  ShowMessage(Test('toto'));
end;

As you can see, this code cannot possibly leak. Run it and, as expected, it does not leak.
Now put a break point on the ShowMessage line and create a watch to execute Test(‘boo’), checking the “Allow side effects and function calls” in the watch dialog. Run the application, it stops before the ShowMessage and you can see the ‘test: boo’ string value in the watch. Resume the execution, you get the ShowMessage and you can close the Application when the Main Form is shown.

Now, you get the dreaded Memory Leak Report dialog:
—————————
Unexpected Memory Leak
—————————
An unexpected memory leak has occurred.
The unexpected small block leaks are:85 – 92 bytes: UnicodeString x 1
—————————

The funny thing is that if you save this project “as is” and close Delphi, next time you restart Delphi and run it, it might not necessary leak even though you see the watch expression evaluated; you’d have to run it again or even open the watch dialog explictly and then boom! you’ll get the leak….
Anyway, as using “Evaluate” or “Watch” are common use cases when debugging, it’s very easy to find yourself with some strange memory leaks.

I have experienced this with D2010 and XE, with or without the full FastMM4. I don’t recall having this with D2007 or previous versions.

Do you Delphites out there see that behavior?
(not yet logged in QC, but it looks very similar to QC# 73762, so maybe that one just needs more visibility and more votes, and this new Use Case).

This entry was posted in Debugger, Delphi, Quality and tagged , , , , , . Bookmark the permalink.

22 Responses to When The Debugger Leaks…

  1. Dany M says:

    Yes, since I started using D2010, but never before. I have seen it a lot and it was only some month ago that I stumbled over a comment somewhere saying that the debugger may cause a leak (a response to something different) that I realized what was going on.

    It’s annoying as hell, borderline dangerous IMHO.

    /D

  2. Tries to “ReportMemoryLeaksOnShutdown := True;” in the project source, not in FormCreate section and then see the result.

    • François Gaillard says:

      Since ReportMemoryLeaksOnShutdown is just a global flag that allows to see or ignore the memory leaks at the end when closing the application, it does not matter where it is set. And the leaks happen anyway, whether it is set or not.

      • batman says:

        good point stanleyxu2005!
        Delphi help file does say to put the ReportMemoryLeaksOnShutdown := True in the prj source.
        I understand that it might be the same then pu it in any place else but I don’t know for sure.
        Does Delphi always collect these info and display them only if ReportMemoryLeaksOnShutdown is true? or it starts to collect data only if ReportMemoryLeaksOnShutdown is true?
        In the secon case it will make a huge difference.
        In any case I would stick with the official instructions..

        • François Gaillard says:

          “Read the source Luke!” 😉
          see …\rtl\sysgetmem.inc

            {Should memory leaks be reported?}
            if ReportMemoryLeaksOnShutdown then
              ScanForMemoryLeaks;
          

          ReportMemoryLeaksOnShutdown is only there to show leaks or not upon exiting the application. So it does not matter where it is set, provided it’s prior to this line of code.

          • Jolyon Smith says:

            You must be mistaken – you cannot possible have read the source because professional developers do not do such things.

            At least, that is what I am told … it seems that *someone* is mistaken. LOL

  3. Stephane Wierzbicki says:

    Bonjour François,

    I definitively see such behaviour but I never thought that the debugger could brings this leaks… I guess that you can safely fill a new QC report.

    BR

    SW

  4. Uli Gerhardt says:

    I see similar leaks in my D2007 quite often. So it probably isn’t related to the Unicode move.

    • Dany M says:

      Hmm… I can not confirm that I did not have the problem in D2007 and simply ignored it then because of many regular leaks. Sorry.

  5. Brian Frost says:

    Yes, I’ve seen this a lot. I realised early on that it must be the debugger because the ‘leak’ only seem to happen when viewing a string parameter and not otherwise.

  6. I have seen similar behaviour in Delphi 2007 and before as well.
    –jeroen

  7. Hmm. I didn’t know anyone still used Watches. I abandoned them when they stopped being usable as memory breakpoints. Now they’re nothing but a less-useful duplicate of the Inspect feature. (Does Inspect do this too?)

    • François Gaillard says:

      Yes. Inspect does this too. I’m pretty sure I actually experienced these leaks through Inspect first.

      • Jolyon Smith says:

        Given this insight (and that the same thought just occurred to me during a debugging session of my own), perhaps someone could explain to me how it makes sense to leave “Watches” enabled in Starter Edition, but remove the “Evaluate/Inspect” capability (which is the case, if the feature matrix is to be believed) ?

        It really does look like Starter Edition is seen more as a “paid for unlimited trial edition”, in which it is not uncommon to have otherwise useful features hobbled or made annoying to use. Rather than a true starter edition intended to win over new users by demonstrating the awesome power of the technological marvel that is Delphi.

  8. Kjetil Gloppen says:

    Hi,
    I’m seeing the same behavior in Borland 2006 (ver 10.0.2288.42541).
    This was a very annoying problem. Seeing first a lot of memory leaks when developing, but when trying to track down the leaks I didn’t get any leaks suddenly. Then I saw the connection with having breakpoints in the code. And then, reading this post, I see that it’s only when using watches (or inspect) that the leaks occur. When I disabled all watches the memory leaks went away.

    Thanks for the post! Unbelievable that this is still i problem in version 2010 and XP!

  9. Sharken says:

    Delphi XE here, and i am seeing the same issues with memoryleak.
    In this project there are some records looking like this (changed to protect the innocent):

    TOtherCache = Record
    BeginData: String;
    Data1: Array[1..5] of string;
    Data2: Array[1..2] of string;
    end;

    TCache = Record
    Details: Array[1..10] of TOtherCache;
    end;

    TTripLookupCacheArray = Array[1..14] of TCache;

    If i now assign a value to BeginData such as this :
    BeginData := Qry.FieldByName(‘field’).AsString;

    FastMM will report a memoryleak has happened.

    To avoid this i need to clear all strings throughout the record.
    If i do that, the memoryleak disappears.

    So if you use records in a similar fashion, i would suggest you try and set all strings to ” to get rid of memory leaks.

    My conclusion is that Delphi does not properly release memory held by strings,
    if those strings belong to a fixed-length record with a complex structure. Maybe this can be fixed in a future version.

    • François Gaillard says:

      You’re not using ThreadVars, are you?
      Because the memory is not managed for those and you have to explicitly clear the strings that are ThreadVars.

    • François Gaillard says:

      Another way to leak a string which is in a record is to call FillChar on it after putting a nonnil string in it. The RefCount gets wiped out by FillChar.

      • Sharken says:

        Good tip about FillChar, the code was using FillChar in places, after removing it the memoryleak went away.

        I think it shows that FastMM is usually correct, when it says there are memoryleaks in the code. Also just a few of these string-related memoryleaks, can make it virtually impossible to find other memoryleaks in the code, very annoying indeed.

  10. Ahmed says:

    I hate this fucking memory leaks !!!

    Why Embarcadero developers do not remove them !?

Leave a Reply

Your email address will not be published. Required fields are marked *