View Issue Details

IDProjectCategoryView StatusLast Update
0037164FPCCompilerpublic2020-06-05 12:38
ReporterFrederic Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
PlatformLinuxOSManjaro 
Product Version3.3.1 
Summary0037164: Double Finalize in Managed Enumerator Records
DescriptionWhen using managed records as enumerator types (via management operators), the finalize will be called twice with only one Initialize, no AddRef and no Copy.

See the example project for further information:
Steps To ReproduceCompile the attached program with fpc 3.2 RC1 or 3.3.1 and run the example.
Additional InformationThe example prints out "init" during the initialize operator, "ref" during AddRef, "copy" during Copy and "final" during Finalize. Result:
init
final
0
1
final

Final is called two times without any Init, Copy or AddRef in between

My guess is that this datastructure is somehow copied without calling the appropriate operator (AddRef or Copy)
TagsNo tags attached.
Fixed in Revision
FPCOldBugId
FPCTarget
Attached Files

Activities

Frederic

2020-06-01 21:21

reporter  

test.pas (1,511 bytes)   
program Project1;

{$mode Delphi}
{$ModeSwitch advancedrecords}

type

  { TRangeEnumerator }

  TRangeEnumerator = record
    FMax: Integer;
    FMin: Integer;
    FValue: Integer;
    property Current: Integer read FValue;
    function CurrentIndex: Integer;
    function MoveNext: Boolean;
    class operator Initialize(var e: TRangeEnumerator);
    class operator Finalize(var e: TRangeEnumerator);
    class operator AddRef(var e: TRangeEnumerator);
    class operator Copy(constref s: TRangeEnumerator; var d: TRangeEnumerator);
  end;

  TRange = record
    min: Integer;
    max: Integer;
    function GetEnumerator(): TRangeEnumerator;
  end;

function TRange.GetEnumerator: TRangeEnumerator;
begin
  Result.FMax := max;
  Result.FMin := min;
  Result.FValue := min - 1;
end;

{ TRangeEnumerator }

function TRangeEnumerator.CurrentIndex: Integer;
begin
  Result := FValue - FMin;
end;

function TRangeEnumerator.MoveNext: Boolean;
begin
  FValue += 1;
  Result := FValue < Fmax
end;

class operator TRangeEnumerator.Initialize(var e: TRangeEnumerator);
begin
  WriteLn('init');
end;

class operator TRangeEnumerator.Finalize(var e: TRangeEnumerator);
begin
  WriteLn('final');
end;

class operator TRangeEnumerator.AddRef(var e: TRangeEnumerator);
begin
  WriteLn('ref');
end;

class operator TRangeEnumerator.Copy(constref s: TRangeEnumerator;
  var d: TRangeEnumerator);
begin
  WriteLn('copy');
end;

var r: TRange;
  i: INteger;
begin
  r.min := 0;
  r.max := 2;
  for i in r do WriteLn(i);
end.

test.pas (1,511 bytes)   

Thaddy de Koning

2020-06-03 12:48

reporter   ~0123200

I can confirm this and on all platforms I use, but the second final appears to be harmless.

Serge Anvarov

2020-06-03 19:40

reporter   ~0123206

Initialization code is called at the beginning. This is normal.
The finalizing code is called before the loop starts. This would also be normal if initialization was called next. But this is not the case, apparently in the expectation that GetEnumerator will do this. But there is no initialization code there either.
The finalizing code is called when it exits the scope.

And the harmlessness is doubtful. Try allocating memory in the initialization code and releasing it in the finalization. Obviously there will be a double memory release error.

Serge Anvarov

2020-06-03 19:53

reporter   ~0123207

Simplified example with a problem.
project1.lpr (1,106 bytes)   
{$MODE OBJFPC}
{$MODESWITCH ADVANCEDRECORDS}
{$APPTYPE CONSOLE}

type
  TSomeManagedRecord = record
    class operator Initialize(var e: TSomeManagedRecord);
    class operator Finalize(var e: TSomeManagedRecord);
    class operator AddRef(var e: TSomeManagedRecord);
    class operator Copy(constref s: TSomeManagedRecord; var d: TSomeManagedRecord);
  end;

function GetSomeManagedRecord: TSomeManagedRecord;
begin
end;

class operator TSomeManagedRecord.Initialize(var e: TSomeManagedRecord);
begin
  WriteLn('Initialize');
end;

class operator TSomeManagedRecord.Finalize(var e: TSomeManagedRecord);
begin
  WriteLn('Finalize');
end;

class operator TSomeManagedRecord.AddRef(var e: TSomeManagedRecord);
begin
  WriteLn('AddRef');
end;

class operator TSomeManagedRecord.Copy(constref s: TSomeManagedRecord;
  var d: TSomeManagedRecord);
begin
  WriteLn('Copy');
end;

procedure Test;
var
  R: TSomeManagedRecord;
begin
  R := GetSomeManagedRecord;  // This OK
  //GetSomeManagedRecord; // This do double finalization
end;

begin
  Test;
  Readln;
end.
project1.lpr (1,106 bytes)   

Frederic

2020-06-05 12:38

reporter   ~0123242

I want to note that the two finalizes are to be expected, GetEnumerator creates the record, so if the GetEnumerator scope ends, it is expected to be finalized. Then after the loop when the enumerator dies it should also be finalized. IMHO there is either a AddRef or a Copy (+ another initialization before the copy) missing in between

Issue History

Date Modified Username Field Change
2020-06-01 21:21 Frederic New Issue
2020-06-01 21:21 Frederic File Added: test.pas
2020-06-03 12:48 Thaddy de Koning Note Added: 0123200
2020-06-03 19:40 Serge Anvarov Note Added: 0123206
2020-06-03 19:53 Serge Anvarov Note Added: 0123207
2020-06-03 19:53 Serge Anvarov File Added: project1.lpr
2020-06-05 12:38 Frederic Note Added: 0123242