Blog
All Blog Posts | Next Post | Previous PostPuzzling 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 applicationprocedure 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;
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;
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;
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;
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;
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.
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.
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!
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
All Blog Posts | Next Post | Previous Post
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