Memory leak in custom-drawn widgetset in RenderWinControl()
- Lazarus/FPC Version: Lazarus 2.3.0 (rev main-2_3-1386-g23b2324f9f) FPC 3.2.3 x86_64-linux-gtk2
- Operating System: any
- CPU / Bitness: any
What happens
By lagprogramming from forum: https://forum.lazarus.freepascal.org/index.php/topic,62955.msg476376.html#msg476376 .
lcl/interfaces/customdrawn/customdrawnproc.pas has function RenderWinControl(var AImage: TLazIntfImage; var ACanvas: TLazCanvas; ACDWinControl: TCDWinControl; ACDForm: TCDForm): Boolean;
A simple form with a TEdit on it run for a couple of seconds in Linux-customdrawn. The application ran with heaptrc unit, which made the console window print lots of memory leaks. One of them was related to line: lRegion := TLazRegionWithChilds.Create;
The memory allocated for lRegion is never freed.
As long as customdrawn is selected, the memory leak should be detectable on all operating systems.
This is the original code
function RenderWinControl(var AImage: TLazIntfImage; var ACanvas: TLazCanvas;
ACDWinControl: TCDWinControl; ACDForm: TCDForm): Boolean;
var
lWinControl, lParentControl: TWinControl;
struct : TPaintStruct;
lCanvas: TCanvas;
lControlCanvas: TLazCanvas;
lBaseWindowOrg: TPoint;
lControlStateEx: TCDControlStateEx;
lDrawControl: Boolean;
lRegion:TLazRegionWithChilds;
begin
Result := False;
lWinControl := ACDWinControl.WinControl;
{$ifdef VerboseCDWinControl}
DebugLn(Format('[RenderWinControl] lWinControl=%x Name=%s:%s Left=%d'
+ ' Top=%d Width=%d Height=%d', [PtrInt(lWinControl), lWinControl.Name, lWinControl.ClassName,
lWinControl.Left, lWinControl.Top, lWinControl.Width, lWinControl.Height]));
{$endif}
if lWinControl.Visible = False then Exit;
// Disable the drawing itself, but keep the window org and region operations
// or else clicking and other things are broken
lDrawControl := ACDWinControl.IsControlBackgroundVisible();
// Save the Canvas state
ACanvas.SaveState;
ACanvas.ResetCanvasState;
// lBaseWindowOrg makes debugging easier
// Iterate to find the appropriate BaseWindowOrg relative to the parent control
lBaseWindowOrg := FindControlPositionRelativeToTheForm(lWinControl);
ACanvas.BaseWindowOrg := Point(lBaseWindowOrg.X, lBaseWindowOrg.Y - ACDForm.ScrollY);
ACanvas.WindowOrg := Point(0, 0);
// Prepare the clippping relative to the form
ACanvas.Clipping := True;
ACDWinControl.Region.Rect := Bounds(lBaseWindowOrg.X, lBaseWindowOrg.Y - ACDForm.ScrollY,
lWinControl.Width, lWinControl.Height);
lRegion := TLazRegionWithChilds.Create;
lRegion.Assign(ACDWinControl.Region);
ACanvas.ClipRegion := lRegion;
lControlCanvas := ACanvas;
if (ACDWinControl.InvalidateCount > 0) and lDrawControl then
begin
ACDWinControl.UpdateImageAndCanvas();
lControlCanvas := ACDWinControl.ControlCanvas;
ACDWinControl.InvalidateCount := 0;
// Special drawing for some native controls
if (lWinControl is TCustomPanel) or (lWinControl is TTabSheet)
or (lWinControl is TCustomPage) or (lWinControl is TNotebook) then
begin
// Erase the background of TPanel controls, since it can draw it's own border, but fails to draw it's own background
// and also erase the background for TTabSheet (children of TPageControl) and TCustomPage (children of TNotebook)
lControlCanvas.SaveState;
lControlCanvas.Brush.FPColor := TColorToFPColor(lWinControl.GetRGBColorResolvingParent());
lControlCanvas.Pen.FPColor := lControlCanvas.Brush.FPColor;
lControlCanvas.Rectangle(Bounds(0, 0, lWinControl.Width, lWinControl.Height));
lControlCanvas.RestoreState(-1);
end
else if lWinControl is TCustomGroupBox then
begin
lControlCanvas.SaveState;
lControlStateEx := TCDControlStateEx.Create;
try
lControlStateEx.Font := lWinControl.Font;
lControlStateEx.Caption := lWinControl.Caption;
lControlStateEx.ParentRGBColor := lWinControl.GetRGBColorResolvingParent();
GetDefaultDrawer().DrawGroupBox(lControlCanvas, Point(0,0),
Size(lWinControl.Width, lWinControl.Height), [], lControlStateEx);
finally
lControlStateEx.Free;
lControlCanvas.RestoreState(-1);
end;
end;
// Send the drawing message
{$ifdef VerboseCDWinControl}
DebugLn('[RenderWinControl] before LCLSendPaintMsg');
{$endif}
FillChar(struct, SizeOf(TPaintStruct), 0);
struct.hdc := HDC(lControlCanvas);
LCLSendEraseBackgroundMsg(lWinControl, struct.hdc);
LCLSendPaintMsg(lWinControl, struct.hdc, @struct);
{$ifdef VerboseCDWinControl}
DebugLn('[RenderWinControl] after LCLSendPaintMsg');
{$endif}
end;
// Here we actually blit the control to the form canvas
if lDrawControl then
ACanvas.CanvasCopyRect(ACDWinControl.ControlCanvas, 0, 0, 0, 0,
lWinControl.Width, lWinControl.Height);
// Now restore it
ACanvas.RestoreState(-1);
Result := True;
end;
The patch adds "lRegion.Free;" before the last "ACanvas.RestoreState(-1);"
diff --git a/lcl/interfaces/customdrawn/customdrawnproc.pas b/lcl/interfaces/customdrawn/customdrawnproc.pas
index 5170ddc72b..aeb36771b2 100644
--- a/lcl/interfaces/customdrawn/customdrawnproc.pas
+++ b/lcl/interfaces/customdrawn/customdrawnproc.pas
@@ -563,6 +563,8 @@ begin
ACanvas.CanvasCopyRect(ACDWinControl.ControlCanvas, 0, 0, 0, 0,
lWinControl.Width, lWinControl.Height);
+ lRegion.Free;
+
// Now restore it
ACanvas.RestoreState(-1);