Memory leak when using constants initialized with translated resource strings
Summary
Programs that use internationalized resource strings in typed constants leak memory due to improper reference counting when translated at runtime.
More specifically, UpdateResourceStringRefs calls fpc_ansistr_assign on the result of a cast to AnsiString on TResStrInitEntry.Addr, which is declared as PPointer. Since there is no corresponding FinalizeResourceStringRefs procedure (to be invoked during finalization) and the global table entries holding the references aren't declared as AnsiString themselves (only transiently cast from Pointer), this results in the string's reference count staying permanently inflated, thus preventing deallocation.
System Information
Found on 3.2.2 - i386-win32, but should be present on all platforms whose RTL defines FPC_HAS_RESSTRINITS.
Steps to reproduce
Compile & run the following example program with -gh to show the leak:
Example Project
program RS_Leak;
{$mode objfpc}{$H+}
resourcestring
S_FOO = 'BAR';
procedure Print_RC(const S: String);
begin
WriteLn(PSizeInt(Pointer(S))[-2]);
end;
function UpdateFoo(Name, Value: String; Hash: Integer; Data: Pointer): String;
begin
Result := Value + Value;
end;
procedure Test();
const
FOO_X1: String = S_FOO; // This alone is enough to cause a leak
var
FOO_X5: array[0..4] of String = (S_FOO, S_FOO, S_FOO, S_FOO, S_FOO);
begin
Print_RC(FOO_X5[0]); // Prints 12 (5 additional local references in FOO_X5)
Print_RC(FOO_X1);
end;
begin
Print_RC(S_FOO); // Prints -1 (referring to constant "BAR")
SetUnitResourceStrings('RS_Leak', @UpdateFoo, Nil);
Print_RC(S_FOO); // Prints 7 (referring to non-constant "BARBAR")
Test();
Print_RC(S_FOO); // Prints 7
end.
What is the current bug behavior?
Translated resource strings used to initialize constants are not freed during unit finalization, leading to memory leaks (visible in heaptrc output).
What is the expected (correct) behavior?
All strings should be properly freed.
Relevant logs and/or screenshots
-1
7
12
12
7
Heap dump by heaptrc unit of "<redacted>\rsleak.exe"
2 memory blocks allocated : 39/48
1 memory blocks freed : 20/24
1 unfreed memory blocks : 19
True heap size : 163840 (208 used in System startup)
True free heap : 163504
Should be : 163512
Call trace for block $01620288 size 19
$00401662 UPDATEFOO, line 15 of RS_Leak.pas
$00410962 SETUNITRESOURCESTRINGS, line 457 of ../objpas/objpas.pp
$00401733 main, line 31 of RS_Leak.pas
Possible fixes
Because of the fragmentation of table data across units (and the multiple levels of indirection à "pointer to table" thus required), I think the best solution would be to add a FinalizeResourceStringRefs procedure that is called during finalization of the objpas unit, much like FinalizeResourceTables.
{ Following UpdateResourceStringRefs near objpas.pp:404 }
procedure FinalizeResourceStringRefs;
var
i: integer;
ptable: PResStrInitEntry;
begin
for i:=1 to ResStrInitTable^.Count do
begin
ptable:=ResStrInitTable^.Tables[i];
while Assigned(ptable^.Addr) do
begin
AnsiString(ptable^.Addr^):='';
Inc(ptable);
end;
end;
end;
{ Preceding FinalizeResourceTables in the finalization section near objpas.pp:527 }
{$ifdef FPC_HAS_RESSTRINITS}
FinalizeResourceStringRefs;
{$endif FPC_HAS_RESSTRINITS}