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}