Search code examples
delphiclosuresanonymous-functiondelphi-10-seattle

Local variable broken by closure capture when accessed in nested method


I've managed to reduce this problem to this :

program Project1;
{$APPTYPE CONSOLE}

uses
  SysUtils, Threading;

procedure Foo(AString: string);
var
  LTask : ITask;
  capturedString : string;
  procedure Nested;
  begin
    try
      WriteLn('Nested : ' + capturedString); { ! EIntOverflow (Win32) here }
    except on E : Exception do
      WriteLn(E.Message);  
    end; 
  end;
begin
  capturedString := AString;
  WriteLn('Local : ' + capturedString);
  Nested;
  LTask := TTask.Create(
    procedure
      procedure AnonNested;
      begin
        WriteLn(capturedString); { Removing this eliminates the problem }
      end;
    begin
    end);
end;

begin
  Foo('foo');
  ReadLn;
end.

Here the capturedString variable gets corrupted when accessed from within a nested method. A Win32 compile raises EIntOverflow, a Win64 compile writes out a (corrupt) empty string - either build can be provoked into AV or other exceptions with some manipulation but in all cases the reference to the local variable is corrupted when entering the Nested procedure.

This seems to only happen if capturedString is captured in a closure.

What's going wrong?


Solution

  • This seems like a compiler bug that is resolved in 10.2 :

    #RSP-18833: Capture by closure corrupts local variable used in nested method

    A workaround is to use a second variable for capture in the anonymous method:

    procedure Foo(AString: string);
    var
      LTask : ITask;
      capturedString, s2 : string;
      procedure Nested;
      begin
        try
          WriteLn('Nested : ' + capturedString);
        except on E : Exception do
          WriteLn(E.Message);  { !!! }
        end; 
      end;
    begin
      capturedString := AString;
      s2 := capturedString;
      WriteLn('Local : ' + capturedString);
      Nested;
      LTask := TTask.Create(
        procedure
          procedure AnonNested;
          begin
            WriteLn(s2); { Capture another variable }
          end;
        begin
        end);
    end;