Recent

Author Topic: Improvement of function Cache_New in components/freetype/ttcache.pas  (Read 1890 times)

lagprogramming

  • Sr. Member
  • ****
  • Posts: 406
  The pascal code in components/freetype/ttcache.pas is one of the weirdest I've seen so far. It has a couple of functions that return TError, where TError type is defined as boolean. The boolean values used are not the traditional true and false, instead of them Success and Failure are used, where:
Code: Pascal  [Select][+][-]
  1. const
  2.   Success = False;
  3.   Failure = True;
 
  As you can see, it's like when you think of yes you have to write no, and when you think of no you have to write yes. This uncommon usage of boolean values is prone to easily insert bugs.
  I've noticed that running an empty form in Linux-customdrawn with heaptrc unit the console writes about a memory leak at line "Alloc( element, sizeof(TList_Element) );" in function Element_New : PList_Element. Following the trail I've looked at the following code:
Code: Pascal  [Select][+][-]
  1.   function Cache_New( var cache      : TCache;
  2.                       var new_object : Pointer;
  3.                       parent_data    : Pointer ) : TError;
  4.   var
  5.     error   : TError;
  6.     current : PList_Element;
  7.     obj     : Pointer;
  8.   label
  9.     Fail;
  10.   begin
  11.     Result := False;
  12.     (* LOCK *)
  13.     current := cache.idle;
  14.     if current <> nil then
  15.     begin
  16.       cache.idle := current^.next;
  17.       dec( cache.idle_count )
  18.     end;
  19.     (* UNLOCK *)
  20.  
  21.     if current = nil then
  22.       begin
  23.         (* if no object was found in the cache, create a new one *)
  24.         obj:=nil;
  25.         if Alloc( obj, cache.clazz^.object_size ) then exit;
  26.  
  27.         current := Element_New;
  28.         if current = nil then goto Fail;
  29.  
  30.         current^.data := obj;
  31.  
  32.         error := cache.clazz^.init( obj, parent_data );
  33.         if error then goto Fail;
  34.       end;
  35.  
  36.     (* LOCK *)
  37.     current^.next := cache.active;
  38.     cache.active  := current;
  39.     (* UNLOCK *)
  40.  
  41.     new_object := current^.data;
  42.  
  43.     Cache_New := Success;
  44.     exit;
  45.  
  46.   Fail:
  47.     Free( obj );
  48.     Cache_New := Failure;
  49.   end;
  50.  
  1/2 Notice that the first line is "Result := False;". This line increases the confusion because mixing false with Failure(true) and Success(false) in the same routine is clearely a bad idea, making the code even harder to understand. I've removed this line by replacing "if Alloc( obj, cache.clazz^.object_size ) then exit;" with "if Alloc( obj, cache.clazz^.object_size ) then goto Fail;"; In the original code, if Alloc would have returned Failure(true) the function would have exited with a false(Success) result, which was obviously a bug.
  2/2 Got back to the memory leak. I've noticed that if Alloc( obj, cache.clazz^.object_size ) = Success(false) and later if error = Failure(true) then the memory allocated at "current := Element_New;" will never be freed. For this reason I've added "Element_Done( current );" before "goto Fail;". I think somebody should double check that Element_Done is enough. "Free( current );" is safer but it might remove a cache entry.
  The above two changes didn't stop the memory leak but I think the code of the function is better.
  If you agree with the changes, here is a patch.
Code: Pascal  [Select][+][-]
  1. diff --git a/components/freetype/ttcache.pas b/components/freetype/ttcache.pas
  2. index f6a4e78549..810ecb2fc7 100644
  3. --- a/components/freetype/ttcache.pas
  4. +++ b/components/freetype/ttcache.pas
  5. @@ -298,7 +298,6 @@ var
  6.    label
  7.      Fail;
  8.    begin
  9. -    Result := False;
  10.      (* LOCK *)
  11.      current := cache.idle;
  12.      if current <> nil then
  13. @@ -312,7 +311,7 @@ var
  14.        begin
  15.          (* if no object was found in the cache, create a new one *)
  16.          obj:=nil;
  17. -        if Alloc( obj, cache.clazz^.object_size ) then exit;
  18. +        if Alloc( obj, cache.clazz^.object_size ) then goto Fail;
  19.  
  20.          current := Element_New;
  21.          if current = nil then goto Fail;
  22. @@ -320,7 +319,11 @@ var
  23.          current^.data := obj;
  24.  
  25.          error := cache.clazz^.init( obj, parent_data );
  26. -        if error then goto Fail;
  27. +        if error then
  28. +          begin
  29. +            Element_Done( current );
  30. +            goto Fail;
  31. +          end;
  32.        end;
  33.  
  34.      (* LOCK *)

Leledumbo

  • Hero Member
  • *****
  • Posts: 8757
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Improvement of function Cache_New in components/freetype/ttcache.pas
« Reply #1 on: April 09, 2023, 06:21:33 pm »
This is where you should file this: https://gitlab.com/freepascal.org/fpc/source/-/issues
let the maintainers decide there.


marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11452
  • FPC developer.
Re: Improvement of function Cache_New in components/freetype/ttcache.pas
« Reply #3 on: April 11, 2023, 02:15:19 pm »
Please repeat anything in the bugtracker, and don't just post a link to the forum in the bugtracker.

AlexTP

  • Hero Member
  • *****
  • Posts: 2402
    • UVviewsoft
Re: Improvement of function Cache_New in components/freetype/ttcache.pas
« Reply #4 on: April 11, 2023, 03:15:18 pm »
Repeated.

 

TinyPortal © 2005-2018