Blog

All Blog Posts  |  Next Post  |  Previous Post

Puzzling a Pascal developer with 33 years experience under the belt

Tuesday, October 1, 2019

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?

Bruno Fierens




This blog post has received 6 comments.


1. Tuesday, October 1, 2019 at 3:16:17 PM

In all win32 calling conventions, the result type of a function (if it is 32bit and smaller and not a managed type) is returned in the eax register. The value of that register on function entry is "undefined".

By calling TStringList.Create (which returns that new object in eax) right before calling TestFunction, eax (which is being accessed as "Result" in the function) just happens to still contain that pointer to the newly created TStringList.

Obviously that''s not valid code and it''s purely accidental that it seems to "work".

Thorsten Engler


2. Tuesday, October 1, 2019 at 8:51:15 PM

Thanks for the technical clarification!

Bruno Fierens


3. Wednesday, October 2, 2019 at 1:52:43 AM

Noise on the stack can some times have unexpected results. Or, in this case, because of register optimization, it is likely the last function result is still loaded into EAX and you got lucky.

My worst stack fail lead my IBM 286 to not just crash, but the screen cleared and it promptly reported a system bus failure.

I''m not sure what is more impressive - that hit just the right noise to jump to that part of the bios, or that some optimistic bios writer figured they could get a meaningful error out if the system bus had indeed failed.

Clinton Johnson


4. Wednesday, October 2, 2019 at 3:39:53 AM

I try not to use code that looks like it shouldn''t work.

It makes it harder to follow, and usually breaks sooner or later (new Delphi version, different compile conditions, change in the code''s "environment").

Once I was writing code in an FMX form and used a variable called index. I didn''t notice that "index" wasn''t declared, and it worked, except there were unexpected results - turns out Index is used by FMX at some point in their class hierarchy and it surfaced for my code, but not it a good way!

van der Linden Scott`


5. Wednesday, October 2, 2019 at 2:59:50 PM

Supporting a developer product must be a very fun job!

Aschbacher Peter


6. Wednesday, October 2, 2019 at 3:23:44 PM

I can''t think of how it must be to do support a software product for computer illiterate people. I think we are blessed to help software developers :)

Bruno Fierens




Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post