Recent

Author Topic: [SOLVED] Heaptrc reports unfreed memory in TForm.ShowModal and Heaptrc can crash  (Read 1114 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 1028
I found a way to reproduce, that when my program terminates normally, that Heaptrc then reports unfreed memory:

On Windows with Lazarus 3.6.0 and FPC 3.2.2 I get:
Code: Text  [Select][+][-]
  1. Heap dump by heaptrc unit of h:\Progs\Lazarus\work\archiv\azul.exe
  2. 189589 memory blocks allocated : 8870975/9269864
  3. 189588 memory blocks freed     : 8870959/9269848
  4. 1 unfreed memory blocks : 16
  5. True heap size : 30769152 (96 used in System startup)
  6. True free heap : 949888
  7. Should be : 30768944
  8. Call trace for block $058EBC18 size 16
  9.   $00410F3E
  10.   $00409432
  11.   $0040A1B9
  12.   $004AD435  UTF16TOUTF8,  line 3971 of lazutf8.pas
  13.   $005F7476  GETCONTROLTEXT,  line 1084 of win32proc.pp
  14.   $00602AC0  TWIN32WSCUSTOMEDIT__GETTEXT,  line 1460 of win32wsstdctrls.pp
  15.   $00588BF8  TWINCONTROL__FINALIZEWND,  line 7686 of ./include/wincontrol.inc
  16.   $00589136  TWINCONTROL__DESTROYWND,  line 7850 of ./include/wincontrol.inc
  17.   $0058474A  TWINCONTROL__DESTROYHANDLE,  line 5334 of ./include/wincontrol.inc
  18.   $00584734  TWINCONTROL__DESTROYHANDLE,  line 5330 of ./include/wincontrol.inc
  19.   $00575FD7  RECREATEWND,  line 3108 of controls.pp
  20.   $00424628  fin$00000906,  line 3083 of ./include/customform.inc       // final part of ShowModal
  21.   $004544F2  TFORMLB__PROCESS_LB,  line 11276 of D:/FPC/work/LCLlib.pas // this calls TForm.ShowModal
  22.   $00454B94  SEARCHLISTBOX00,  line 11419 of D:/FPC/work/LCLlib.pas
  23.   $00454C21  SEARCHLISTBOX0,  line 11437 of D:/FPC/work/LCLlib.pas
  24.   $00454C98  SEARCHLISTBOX,  line 11451 of D:/FPC/work/LCLlib.pas

On Linux with Lazarus 3.4.0 and FPC 3.2.2 I get:
Code: Text  [Select][+][-]
  1. Heap dump by heaptrc unit of /hg/utis/azul
  2. 187868 memory blocks allocated : 14717795/15157560
  3. 187867 memory blocks freed     : 14717769/15157528
  4. 1 unfreed memory blocks : 26
  5. True heap size : 57638912
  6. True free heap : 1033856
  7. Should be : 57638688
  8. Call trace for block $00007F48BF9EE900 size 26
  9. An unhandled exception occurred at $0000000000446B49:
  10. EInOutError:
  11.   $0000000000446B49
which means that Heaptrc always (!) crashes. I tried valgrind on Linux with option --tool=memcheck and a 2nd time with --leak-check=full but did not get any helpful results.

The mentioned source on Windows in ./include/customform.inc is the final part of ShowModal (line 3083 is marked yellow):
Code: Pascal  [Select][+][-]
  1.     finally
  2.       RestoreFocusState(SavedFocusState);
  3.       Screen.EndScreenCursor;
  4.       if LCLIntf.IsWindow(ActiveWindow) then
  5.         SetActiveWindow(ActiveWindow);
  6.       Exclude(FFormState, fsModal);
  7.       if ((PopupMode = pmNone) and HandleAllocated) and not (csDestroying in ComponentState) then
  8.         RecreateWnd(Self); // need to refresh handle for pmNone because ParentWindow changes if (fsModal in FFormState) - see GetRealPopupParent
  9.     end;
  10.   finally
  11.     Application.ModalFinished;
  12.   end;
  13. end;

I'm not so familiar with the sources of the LCL and understand not much of what's going on there.
Question 1:
Has anybody an idea, what could be the problem in the above part of ShowModal?

I checked my class, which calls 'ShowModal', multiple times, but did not see something wrong. Especially nothing, what could cause a problem inside of ShowModal...

Here is my class's definition to give a rough impression (reduced by some unimportant things):
Code: Pascal  [Select][+][-]
  1. type
  2.    ansi = ansistring;
  3.    s80 = string[80];
  4.    long = longint;
  5.    bool = boolean;
  6.  
  7.    TFormLB = class(TForm)       {dynamic Form for a ListBox with search filter: }
  8.       private
  9.    EditLB: TEdit;      {the search filter}
  10.    LBS: TListBox;      {searchable ListBox}
  11.    SLS: TStringList;   {contains always ALL existing items}
  12.    ISL: array of long; {contains only the indices of the filtered items}
  13.    exItem: long;       {selected tem (0..) from 'LBS' / -1 = nothing selected}
  14.    suchJN: bool;       {display search filter 'EditLB' or not?}
  15.    eventDone: bool;    {was Event EditLB_OnChange() already called?}
  16.  
  17.    procedure LB_update_Liste; {fills LBS.Items[] and ISL[] by current 'EditLB.Text'}
  18.  
  19.    procedure EditLB_OnChange(Sender: TObject);                         {Events: }
  20.    procedure EditLB_OnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  21.    procedure LBS_OnClick(Sender: TObject);
  22.    procedure LBS_OnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  23.    procedure Form_OnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  24.    procedure Form_OnClose_LB(Sender: TObject; var CloseAction: TCloseAction);
  25.    procedure Form_OnDestroy_LB(Sender: TObject);
  26.                                                                  {main routine: }
  27.    function process_LB(var SL: TStringList; item1: long; var such: ansi): long;
  28.  
  29.                                              {initializations called only once: }
  30.    procedure init_LB(x0,y0,xb,yh, itemHeight,scrollWidth: integer; suchJN0: bool);
  31.    procedure create_dyn_controls_LB;
  32.    procedure set_all_positions_LB(x0,y0, xb,yh: integer);
  33.    procedure init_controls_LB(itemHeight,scrollWidth: integer);
  34.    end; {class}
This class is created with FormLB:=TFormLB.CreateNew(nil) and displays a dynamic Form with a ListBox and a TEdit, which is used as a filter, so you can reduce the displayed data to only that data, which contains certains substrings.

I hesitate to attach the whole source because it's a lot of lines and this class calls a lot of my own procedures from other units (which again call a lot of other of my own procedures from other units... and so on...)

Question 2:
Has anybody an idea, what could cause the mentioned problem in the above showed source (line 3083) of ShowModal, which could be caused by such a Class?
Please keep in mind, that valgrind on Linux did not report any helpful results. Thanks in advance.
« Last Edit: November 10, 2025, 04:18:45 pm by Hartmut »

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12565
  • FPC developer.
Does the problem occur when triggering a shutdown (e.g. ALT-F4) while popping something up with showmodal ?

I had some problems when creating a form with an owner, which was ok when the user closed the popup, and the free removed itself by the owner.

However when shutting down with ALT-F4 the owner freed the popped up form, and then the .FREE in the finally  did interesting things ;-)

Hartmut

  • Hero Member
  • *****
  • Posts: 1028
Does the problem occur when triggering a shutdown (e.g. ALT-F4) while popping something up with showmodal ?

Thanks marcov for your reply. I'm not sure whether I understood your question correctly. I think the answer is 'no'.
 - ShowModal is only called to display the dynamic Form of type 'TFormLB'.
 - ALT-F4 is never pressed.
 - The dynamic Form of type 'TFormLB' is closed by the "X" in the upper right corner of it's Form.
 - After that, the main program is terminated via a normal button.
 - Event 'TFormLB.Form_OnClose_LB' does not set var 'CloseAction', so the dynamic Form is closed manually by 'Free'.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11917
  • Debugger - SynEdit - and more
    • wiki
About the mem leak, is that the only trace, or are there more leaks?

Because that leak does NOT show the location of the leak, but only where the mem was allocated.

RecreateWnd saves the caption (as string / this creates the string, this is where the mem is allocated) => that is ok.

But, then either
- the entire object on which the string is stored (In TControl.FCaption) is leaked
- Or if that string is assigned to any other variable/field at any time later, anywhere else,.. and that is then leaked

So you need to find were either of those 2 happens. Usually that means there is some object leaked that holds the string (as strings do not leak on their own, unless you have severe mem corruption, which would mean you have much more severe problems).

Usually the leak trace for the object in question comes later...

Then again, given that printing the traces crashes itself, it is possible you have some memory corruption. E.g. something wrote out of bounds (behind/before  allocated mem / via pointer) or to an already freed block of mem, or something like this. => that could corrupt the data of the allocated/freed mem lists, and then cause a crash when the leaks a printed.


----------------
If you can run on Linux, try "valgrind --tool=memcheck" (compile with -gv for valgrind / switch heaptrc OFF)

If not use Heaptrc, and set "keepreleased=true" in environment (google). But that is much less helpful than valgrind.

Hartmut

  • Hero Member
  • *****
  • Posts: 1028
Thanks a lot Martin_fr for your detailed reply.

About the mem leak, is that the only trace, or are there more leaks?
I'm not sure how to translate 'leak' correctly in your complete post. I assume, you mean the "unfreed memory" which is reported by Heaptrc? From all what I know, there are no more leaks.

Quote
RecreateWnd saves the caption (as string / this creates the string, this is where the mem is allocated) => that is ok.

But, then either
- the entire object on which the string is stored (In TControl.FCaption) is leaked
- Or if that string is assigned to any other variable/field at any time later, anywhere else,.. and that is then leaked

So you need to find were either of those 2 happens. Usually that means there is some object leaked that holds the string (as strings do not leak on their own, unless you have severe mem corruption, which would mean you have much more severe problems).
This sounded very interesting, because my program always updated the number of filtered items in the Caption of the Form, which was displayed by ShowModal. I commented this out, but unfortunately the report of Heaptrc stays unchanged, so this was not the reason. I searched my whole class for the word 'caption' and found no more matches. Did you mean this? Or should I check for more?

Quote
Then again, given that printing the traces crashes itself, it is possible you have some memory corruption. E.g. something wrote out of bounds (behind/before  allocated mem / via pointer) or to an already freed block of mem, or something like this. => that could corrupt the data of the allocated/freed mem lists, and then cause a crash when the leaks a printed.
I get this problematic Heaptrc output only, if I called this dynamic Form (with a ListBox and a TEdit), before I terminate my program normally.

Quote
If you can run on Linux, try "valgrind --tool=memcheck" (compile with -gv for valgrind / switch heaptrc OFF)
As I wrote I did this already, but the result contained nothing what helped me: only a couple of nearly the same "Conditional jump or move depends on uninitialised value(s)" for SYSTEM_$$_INDEXBYTE$formal$INT64$BYTE$$INT64, where I don't see something wrong. They occur also, if I do not call the dynamic Form (with a ListBox and a TEdit), in which case Heaptrc reports no problems.

The rest of the report is:
Code: Text  [Select][+][-]
  1. ==18494== HEAP SUMMARY:
  2. ==18494==     in use at exit: 2,317,895 bytes in 32,621 blocks
  3. ==18494==   total heap usage: 566,908 allocs, 534,287 frees, 47,086,610 bytes allocated
  4. ==18494==
  5. ==18494== LEAK SUMMARY:
  6. ==18494==    definitely lost: 14,158 bytes in 48 blocks
  7. ==18494==    indirectly lost: 143,002 bytes in 5,804 blocks
  8. ==18494==      possibly lost: 35,799 bytes in 685 blocks
  9. ==18494==    still reachable: 1,974,712 bytes in 24,854 blocks
  10. ==18494==         suppressed: 0 bytes in 0 blocks
  11. ==18494== Rerun with --leak-check=full to see details of leaked memory
  12. ==18494==
  13. ==18494== Use --track-origins=yes to see where uninitialised values come from
  14. ==18494== For lists of detected and suppressed errors, rerun with: -s
  15. ==18494== ERROR SUMMARY: 117 errors from 10 contexts (suppressed: 0 from 0)

Quote
If not use Heaptrc, and set "keepreleased=true" in environment (google). But that is much less helpful than valgrind.
I used Heaptrc with and without 'keepreleased:=true;' but it made no difference.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11917
  • Debugger - SynEdit - and more
    • wiki
I'm not sure how to translate 'leak' correctly in your complete post. I assume, you mean the "unfreed memory" which is reported by Heaptrc? From all what I know, there are no more leaks.
Yes: "leak" = "unfreed mem"

Strange
Quote
Code: Pascal  [Select][+][-]
  1.   $004AD435  UTF16TOUTF8,  line 3971 of lazutf8.pas
  2.   $005F7476  GETCONTROLTEXT,  line 1084 of win32proc.pp
This is clearly a string (ansistring) that is allocated by Utf16ToUtf8. (I don't think anything inside Utf16ToUtf8 leaks, that would have been reported by lots of people already).

But ansistring don't normally leak on their own. That is extremely hard to get to happen.
Well, ok, you can do...
Code: Pascal  [Select][+][-]
  1. pointer(myansistring) := nil;

Or you compiled with "implicitExceptions OFF", and had an exception that prevented the string from being released (normally exceptions don't prevent that)

But then, that would mean, that at some point something gets that caption, assign it to some variable that is either cleared by typecast, fillchar, or freemem (if the var is a field in some hand allocated mem)....
Otherwise, Ansistring are refcounted.
- global/local var will be released automatically
- fields in object too, if the object is freed (and if it isn't then it would need to show as leak of its own)

But yes
Quote
Code: Text  [Select][+][-]
  1. 1 unfreed memory blocks
"1" should have spotted that.

Looking at
Quote
Code: Text  [Select][+][-]
  1. size 16
Would the caption of that window be short enough to fit? 

Actually, that would mean 4 or less chars. (Since the string header takes the rest of the memory).

And the "26" bytes on Linux match that "max 4 char", since your Linux trace seems 64bit, and the Windows seems 32bit (diff size for string header)



Quote
As I wrote I did this already, but the result contained nothing what helped me: only a couple of nearly the same "Conditional jump or move depends on uninitialised value(s)" for SYSTEM_$$_INDEXBYTE$formal$INT64$BYTE$$INT64, where I don't see something wrong.

Sorry, I didn't spot the "done valgrind".

When you did, did you check "Compile for valgrind -gv"?

If you did not, then valgrind will not detect the leaks (and strangely in your case it has not even detected the one known leak).

When you check that, then you must uncheck the "heaptrc" checkbox.

Hartmut

  • Hero Member
  • *****
  • Posts: 1028
Thanks a lot Martin_fr for trying to help me.

But ansistring don't normally leak on their own. That is extremely hard to get to happen.
Well, ok, you can do...
Code: Pascal  [Select][+][-]
  1. pointer(myansistring) := nil;
I'm not sure to understand: is this a suspicion, what I could have done to produce this leak? In this case: I'm sure that I never did something like that. Or is this a suggestion, what I should try now to avoid the leak?

Quote
Or you compiled with "implicitExceptions OFF", and had an exception that prevented the string from being released (normally exceptions don't prevent that)
Do you mean Compiler directive $IMPLICITEXCEPTIONS? I never used this.

Quote
But then, that would mean, that at some point something gets that caption, assign it to some variable that is either cleared by typecast, fillchar, or freemem (if the var is a field in some hand allocated mem)....
Otherwise, Ansistring are refcounted.
- global/local var will be released automatically
- fields in object too, if the object is freed (and if it isn't then it would need to show as leak of its own)
Do I understand you correctly, that you assume, that the caption (the title) of a TForm could/should/must be involved in the leak? If yes: must this Form be the dynamic Form (with a ListBox and a TEdit), which is displayed by that ShowModal, in which the leak occurs? Or could it be the caption of the Main-Form of my program, which has a Button to call the dynamic Form (and when the dynamic Form is closed, the Main-Form has to be displayed again)?

Quote
Looking at
Quote
Code: Text  [Select][+][-]
  1. size 16
Would the caption of that window be short enough to fit?
Actually, that would mean 4 or less chars. (Since the string header takes the rest of the memory).
And the "26" bytes on Linux match that "max 4 char", since your Linux trace seems 64bit, and the Windows seems 32bit (diff size for string header)
You are right: on Windows I compile with 32 bit, while on Linux I compile with 64 bit.
The caption (title) of the Main-Form has 9 characters (pure ASCII, nothing like äöüß etc.).
The caption (title) of the dynamic Form has 10 or 11 characters (pure ASCII, nothing like äöüß etc.).
I attached a screenshot, where you can see in front the dynamic Form (with a ListBox (marked blue) and a TEdit (marked green)) and the Main-Form behind. But as I wrote: I disabled the source line to set the caption of the dynamic Form temporarily, but the report of Heaptrc stayed unchanged, so this seems not the be the reason (from my understanding).
I searched my whole class for the word 'caption' and found no more matches. Did you mean this? Or should I check for more?

Quote
Sorry, I didn't spot the "done valgrind".
When you did, did you check "Compile for valgrind -gv"?
If you did not, then valgrind will not detect the leaks (and strangely in your case it has not even detected the one known leak).
When you check that, then you must uncheck the "heaptrc" checkbox.
Yes, "Generate code for valgrind (-gv)" was checked and the "Use Heaptrc unit" checkbox was unchecked.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11917
  • Debugger - SynEdit - and more
    • wiki
The "pointer(myansistring)"  where examples on how to leak an ansistring. Such code is rather unlikely (but not unseen). I did not assume that you used it. I just generally wanted to point that out. Because of there not being any other leak.

I still wonder what else may be happening. In my experience, Ansistring unfreed mem is usually a side effect of something else...

Or I am mistaken and it is not a string, but then the trace really looks like...

Quote
The caption (title) of the dynamic Form has 10 or 11 characters

Then it is too big to cause fit into the reported unfreed memory (if adding the space for the string header too).

Actually, again my fault, I overlooked some part.
From the trace
Quote
Code: Text  [Select][+][-]
  1.   $005F7476  GETCONTROLTEXT,  line 1084 of win32proc.pp
  2.   $00602AC0  TWIN32WSCUSTOMEDIT__GETTEXT,  line 1460 of win32wsstdctrls.pp

So there is some edit control on the form (TEdit, TMemo, ...) and that calls GetText (afaik getting the user input text).

Question then is what happens to
- the edit control? I would guess/hope that it is just part of the form, and gets destroyed with the form? No special code to create/destroy it?
- the text that the user did input? Does your code process that text? what does it do with that text?
  (and is that user input 1 to 4 chars in len?)

Because if your code processes that text, then (as the string is a pointer) the memory allocated for the text will be kept. I.e. your code takes over the allocated mem. 

It is also possible that the code from one of the controls does something wrong (maybe when used in certain combinations...).

Normally that string would eventually go out of scope, and then the mem would be freed. Question is, why does it not.

Question is also, why did it not happen in valgrind?

Can it be reproduced in a smaller example?



Sorry, not my best this week... Apparently...
Overlooked to many details in your post.

Hartmut

  • Hero Member
  • *****
  • Posts: 1028
Hello Martin, again thanks for your post and your ideas. You are right, a 'TEdit' is involved...

I could reduce the code to reproduce the issue dramatically to a very short demo, which I attached as a compilable project):
Code: Pascal  [Select][+][-]
  1. {This demo makes no sense. It's an extract of a bigger project just to demonstrate the issue}
  2.  
  3. unit Unit1;
  4.  
  5. {$mode objfpc}{$H+}
  6.  
  7. interface
  8.  
  9. uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  10.  
  11. type
  12.  TForm1 = class(TForm)
  13.   Button1: TButton;
  14.   Edit1: TEdit;
  15.   procedure test(var such: ansistring);
  16.   procedure Button1Click(Sender: TObject);
  17.  private
  18.  public
  19.  end;
  20.  
  21. var
  22.  Form1: TForm1;
  23.  
  24. implementation
  25.  
  26. {$R *.lfm}
  27.  
  28. { TForm1 }
  29.  
  30. procedure TForm1.test(var such: ansistring);
  31.    begin
  32.    Edit1.Text:=such;
  33. // ... {do a couple of things, which might change 'such' or not}
  34.    such:=Edit1.Text;
  35.    end;
  36.  
  37. // const such: ansistring = 'x'; {issue does NOT occur with global const}
  38.  
  39. procedure TForm1.Button1Click(Sender: TObject);
  40.    const such: ansistring = 'x'; {issue occurs ONLY with local const}
  41.    begin
  42.    test(such);
  43.    end;
  44.  
  45. end.

After compiling with "Use Heaptrc unit" checkbox activated and running I get on Windows with Lazarus 3.6.0 and FPC 3.2.2 (32-bit) again 16 unfreed bytes:
Code: Text  [Select][+][-]
  1. Heap dump by heaptrc unit of project1.exe
  2. 1357 memory blocks allocated : 1550699/1552312
  3. 1356 memory blocks freed     : 1550683/1552296
  4. 1 unfreed memory blocks : 16
  5. True heap size : 753664 (128 used in System startup)
  6. True free heap : 753408
  7. Should be : 753424
  8. Call trace for block $00283A68 size 16
  9.   $0040F98E
  10.   $004089B2
  11.   $00409709
  12.   $00431315  UTF16TOUTF8,  line 3971 of lazutf8.pas
  13.   $0054DB56  GETCONTROLTEXT,  line 1084 of win32proc.pp
  14.   $00558FB0  TWIN32WSCUSTOMEDIT__GETTEXT,  line 1460 of win32wsstdctrls.pp
  15.   $00503009  TWINCONTROL__REALGETTEXT,  line 8302 of ./include/wincontrol.inc
  16.   $0051FD75  TCUSTOMEDIT__REALGETTEXT,  line 539 of ./include/customedit.inc
  17.   $0050AC89  TCONTROL__GETTEXT,  line 3614 of ./include/control.inc
  18.   $0042BCCB  TFORM1__TEST,  line 34 of unit1.pas
  19.   $0042BD0A  TFORM1__BUTTON1CLICK,  line 42 of unit1.pas
  20.   $005093E9  TCONTROL__CLICK,  line 2974 of ./include/control.inc
  21.   $005226E0  TBUTTONCONTROL__CLICK,  line 55 of ./include/buttoncontrol.inc
  22.   $00522D98  TCUSTOMBUTTON__CLICK,  line 169 of ./include/buttons.inc
  23.   $005225CD  TBUTTONCONTROL__WMDEFAULTCLICKED,  line 21 of ./include/buttoncontrol.inc
  24.   $0040D223

and on Linux with Lazarus 3.4.0 and FPC 3.2.2 (64-bit) again 26 unfreed bytes:
Code: Text  [Select][+][-]
  1. Heap dump by heaptrc unit of /hg/utis/project1
  2. 885 memory blocks allocated : 1683281/1684312
  3. 884 memory blocks freed     : 1683255/1684280
  4. 1 unfreed memory blocks : 26
  5. True heap size : 1638400
  6. True free heap : 1638144
  7. Should be : 1638176
  8. Call trace for block $000077F62AA4BD00 size 26
  9.   $0000000000756980
  10.   $00000000005405F1
  11.   $000000000059F728
  12.   $000000000054A329
  13.   $0000000000465303
  14.   $000000000046536E
  15.   $0000000000548797
  16.   $00000000005A3859
  17.   $00000000005A403A
  18.   $00000000005A3752
  19.   $00000000004310CA
  20.   $000000000053A358
  21.   $0000000000747202
  22.   $00000000006083B0
  23.   $0000000000754FB3
  24.   $000077F62D4A02FA

Strange: the issue disappears completely, if I modify const 'such' to be global (line 37) instead of local (line 40)!

Because Lazarus 3.6.0 ist not very new: could please someone, who has trunk or nearly trunk, try to reproduce this issue?
 - compile and run the attached project in a terminal (to see the Heaptrc output after the program terminates)
 - press "Button1" (then the 'TEdit' will show a 'x')
 - terminate the program with the "X" in the upper right corner.
Then you should see the Heaptrc output.
Please report also your OS and Lazarus version - thanks a lot!

bytebites

  • Hero Member
  • *****
  • Posts: 765
Simpler test code. Leaks in trunk-version too.
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. uses sysutils;
  3.  
  4. procedure test(var s:string);
  5. begin
  6.   s:=inttostr(random(3));
  7. end;
  8.  
  9. procedure Button1Click;
  10.    const such: ansistring = 'x'; {issue occurs ONLY with local const}
  11.    begin
  12.    test(such);
  13.    end;
  14. begin
  15.   Button1Click;
  16.  
  17. end.
  18.          

Hartmut

  • Hero Member
  • *****
  • Posts: 1028
Thanks bytebites for testing this and making it simpler.
Did you test, if the issue is gone, if you make const 'such' global?
Which OS did you use?
Looks to me that it could be a bug. Can you please write your used trunk-version for a bug report?
Thanks.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11917
  • Debugger - SynEdit - and more
    • wiki
Ah, local "const" => that is a hidden global var. It persists between calls like a global, but it can't be accessed from elsewhere.

https://gitlab.com/freepascal.org/fpc/source/-/issues/35536

Hartmut

  • Hero Member
  • *****
  • Posts: 1028
Thanks Martin for this answer and the link. I feel sad, that this is a more than 6 (!) years old bug. It did cost me plenty of time and plenty of nerves :-((

I changed the typed const in my bigger "real project" from local to global and not only the issue is gone, also Heaptrc does not crash any longer on Linux.

BrunoK

  • Hero Member
  • *****
  • Posts: 751
  • Retired programmer
It is a problem of managed strings with writable consts.

The following very simple program also leaks.
Code: Pascal  [Select][+][-]
  1. program pgmLocalStrConst;
  2.  
  3. procedure DoNothing;
  4. const
  5.   such: string = 'x'; {issue occurs ONLY with local const}
  6. begin
  7.   such := such + 'x';
  8. end;
  9.  
  10. begin
  11.   DoNothing;
  12. end.          

Call trace for block $00000000001122A0 size 35
  $0000000100005A62  GETMEM,  line 284 of ../inc/heap.inc
  $0000000100002CB7  NEWANSISTRING,  line 135 of ../inc/astrings.inc
  $000000010000345E  fpc_ansistr_setlength,  line 827 of ../inc/astrings.inc
  $000000010000301D  fpc_ansistr_concat,  line 293 of ../inc/astrings.inc
  $0000000100001629  DoNothing,  line 7 of pgmLocalStrConst.lpr
  $0000000100001643  $main,  line 11 of pgmLocalStrConst.lpr
  $0000000100001656  MAIN_WRAPPER,  line 67 of system.pp
  $000000010000A8E0  EXE_ENTRY,  line 181 of system.pp
  $00000001000015F0  _FPC_MAINCRTSTARTUP,  line 106 of sysinit.pp
  $00007FFE075F7374
  $00007FFE0823CC91

BrunoK

  • Hero Member
  • *****
  • Posts: 751
  • Retired programmer
Additional testing if useful to developers :

Code: Pascal  [Select][+][-]
  1. program pgmLocalStrConst;
  2.  
  3. procedure DoNothing;
  4. const
  5.   such: string = 'x'; {issue occurs ONLY with local const}
  6. begin
  7.   WriteLn(HexStr(QWord(@such), 8), ' ', HexStr(QWord(@such[1]), 8):9);
  8.   such := such + 'x'; // Here, the compiler should handle local constants having
  9.                       // been moved to heap, maybe a list of modified const's to
  10.                       // free when the program ends.
  11.                       //
  12.                       // "such" string being a pointer to an ansi string  (AnsiRec)
  13.                       // record one can see that its content have been moved to
  14.                       // to a new location.
  15.   WriteLn(HexStr(QWord(@such), 8), ' ', HexStr(QWord(@such[1]), 8):9);
  16. end;
  17.  
  18. begin
  19.   DoNothing;
  20. end.

 

TinyPortal © 2005-2018