Recently we received via our support a remark that the following Object Pascal code failed in a TMS WEB Core application
procedure TWebForm1.Button1Click(Sender: TObject); function TestFunction: TStringList; begin Result.Clear; Result.Add('ABC'); Result.Add('DEF'); Result.Add('GHI'); end; var StringList: TStringList; begin StringList := TStringList.Create; try WebMemo1.Lines.Text := TestFunction.Text; finally StringList.Free; end; end;My immediate reaction and answer was "Of course, it is normal and expected this fails. One should create the result instance of a function inside the function".
But then came the reaction: "Well, it works in a VCL application, so it should work in TMS WEB Core too, not?" So, I tried the exact same code in a VCL application (with Delphi 10.2) where I added just a TMemo and a TButton and code:
procedure TForm1.Button1Click(Sender: TObject); function TestFunction: TStringList; begin Result.Clear; Result.Add('ABC'); Result.Add('DEF'); Result.Add('GHI'); end; var StringList: TStringList; begin StringList := TStringList.Create; try Memo1.Lines.Text := TestFunction.Text; finally StringList.Free; end; end;and press compile & run and .... magic ... the compiler gave a warning but the code did add the 3 lines of text to the memo control. I thought that this must be some coincidence with the local variable TStringList memory overlapping the function Result memory and that for sure when compiling this for Win64 it would fail. And guess what... it keeps working! This is the point where I start to doubt everything I learned in 33 years of using the Pascal language. So, next step is testing this exact same code with the FPC compiler from Lazarus 2.0.2. My self-confidence is slowly going to zero as also there the code does add the 3 lines to the memo.
More playing around and changing the code to
function TestFunction: TStringList; begin Result.Clear; Result.Add('ABC'); Result.Add('DEF'); Result.Add('GHI'); end; procedure TForm1.Button1Click(Sender: TObject); var StringList: TStringList; begin StringList := TStringList.Create; try Memo1.Lines.Text := TestFunction.Text; finally StringList.Free; end; end;and the application is still doing the same, adding the 3 lines of text to the memo...
Not wanting to believe that 33 years of experience have been proven useless, I start playing with the code and modify it to:
procedure TForm1.Button1Click(Sender: TObject); function TestFunction: TStringList; begin Result.Clear; Result.Add('ABC'); Result.Add('DEF'); Result.Add('GHI'); end; var StringList: TStringList; s: string; begin StringList := TStringList.Create; s := 'Hello world'; try Memo1.Lines.Text := TestFunction.Text; Memo1.Lines.Add(s); finally StringList.Free; end; end;Aha, self-confidence is slowly coming back again as this fails gloriously with an access violation.
A small twist to the code:
procedure TForm1.Button1Click(Sender: TObject); function TestFunction: TStringList; begin Result.Clear; Result.Add('ABC'); Result.Add('DEF'); Result.Add('GHI'); end; var StringList: TStringList; s: string; begin s := 'Hello world'; StringList := TStringList.Create; try Memo1.Lines.Text := TestFunction.Text; Memo1.Lines.Add(s); finally StringList.Free; end; end;makes it 'work' again.
So, thinking that a class instance created just before invoking a function returning this class type will "just work" from Delphi, I now test it with the compiler set to Release mode instead of the default Debug mode and kaboom, the access violation now always comes. So, now I'm finally sure. One should never rely on creating a class instance outside a function returning this class type. The TMS WEB Core pas2js compiler also causes the equivalent error, i.e. invoking a method of a null, so relief, all is as expected in TMS WEB Core web client applications!
I'm curious to hear if you encountered similar confusing code patterns in your Delphi career?