Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Critical Win64 compiler code generation bug.



Permlink Replies: 9 - Last Post: Apr 2, 2017 8:59 AM Last Post By: Farshad Mohajeri
Farshad Mohajeri

Posts: 120
Registered: 12/28/06
Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 31, 2017 11:26 AM
I post this here because it is a critical bug which I think must be addressed in next update:

Please check below code:

procedure TFormBug.Button1Click(Sender: TObject);
var
  B : TObject;
begin
  B := nil;
  try
    Exit;
 
  finally
 
    try
      raise Exception.Create('Error Message');
    finally
      B.Free; // Exception occurs here. Normally B must be nil, but somehow it contains random bits!
    end;
 
  end;
end;


In Win64 above code it raises an AV at line: B.Free.
It seems that procedure local variable stack is somehow corrupted or overwritten.
In Win32 it works OK.

This bug seems to exist in all Delphi versions from XE2 to 10.2.

Please vote for this QC entry:
https://quality.embarcadero.com/browse/RSP-17707

Such compiler bugs scares me! Especially in server applications where such bugs can easily lead to memory corruption and server crash!

--
Farshad Mohajeri
http://www.unigui.com
Paulo França La...

Posts: 16
Registered: 1/26/17
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 31, 2017 11:39 AM   in response to: Farshad Mohajeri in response to: Farshad Mohajeri
Farshad Mohajeri wrote:
In Win32 it works OK.

Freeing an unassigned object "works ok"?
Farshad Mohajeri

Posts: 120
Registered: 12/28/06
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 31, 2017 11:52 AM   in response to: Paulo França La... in response to: Paulo França La...
Paulo França Lacerda wrote:
Farshad Mohajeri wrote:
In Win32 it works OK.

Freeing an unassigned object "works ok"?

Yes. Delphi does not Free nil objects.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 31, 2017 12:25 PM   in response to: Farshad Mohajeri in response to: Farshad Mohajeri
Farshad wrote:

Delphi does not Free nil objects.

Calling Free() on a nil object pointer is perfectly safe. But calling
Free() on a non-nil pointer that does not point at a valid object is not
safe.

The example you have shown claims the pointer is not nil when it is expected
to be nil. But when I test it in XE2, the exception bypasses the finally
block completely (the RTL ends up executing the 'try' block twice - creating
two exceptions objects - and then jumps into an UnhandledException handler
instead of the 'finally' block):

procedure Test;
var
  B : TObject;
begin
  B := nil;
  try
    Exit;
  finally
    try
      raise Exception.Create('Error Message'); // <-- executed twice
    finally
      B.Free; // <-- never reaches here!
    end;
  end;
end;


Raising a new exception inside a 'finally' block is not a good idea in general,
even if you have a try/except to catch it:

procedure Test;
var
  B : TObject;
begin
  B := nil;
  try
    Exit;
  finally
    try
      try
        raise Exception.Create('Error Message'); // <-- executed twice
      finally
        B.Free; // <-- never reaches here!
      end;
    except
      // never reaches here!
    end;
  end;
end;


procedure Test;
var
  B : TObject;
begin
  B := nil;
  try
    Exit;
  finally
    try
      raise Exception.Create('Error Message'); // <-- executed twice
    except
      // never reaches here!
    end;
    B.Free; // <-- never reaches here!
  end;
end;


Oddly, the 'except' does not catch either exception, but a 'except' further
up the call stack catches the second exception:

procedure Test;
var
  B : TObject;
  I: Integer;
begin
  B := nil;
  I := 0;
  try
    Exit;
  finally
    try
      // this block is executed twice!
      Inc(I);
      raise Exception.CreateFmt('Error Message %d', [I]);
    except
      // never reaches here!
    end;
    B.Free; // <-- never reaches here!
  end;
end;
 
procedure DoTest;
begin
  try
    Test;
  except
    // reaches here! The caught object says 'Error Message 2'
  end;
end;


--
Remy Lebeau (TeamB)
Farshad Mohajeri

Posts: 120
Registered: 12/28/06
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 31, 2017 12:44 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Farshad wrote:

Delphi does not Free nil objects.

Calling Free() on a nil object pointer is perfectly safe. But calling
Free() on a non-nil pointer that does not point at a valid object is not
safe.

The example you have shown claims the pointer is not nil when it is expected
to be nil. But when I test it in XE2, the exception bypasses the finally
block completely (the RTL ends up executing the 'try' block twice - creating
two exceptions objects - and then jumps into an UnhandledException handler
instead of the 'finally' block):

procedure Test;
var
  B : TObject;
begin
  B := nil;
  try
    Exit;
  finally
    try
      raise Exception.Create('Error Message'); // <-- executed twice
    finally
      B.Free; // <-- never reaches here!
    end;
  end;
end;

XE2 seems to work OK.
Please try on a more recent Delphi version. I tested on 10.1 and 10.2.
In my test B is corrupted when B.Free is called.

Farshad Mohajeri

Posts: 120
Registered: 12/28/06
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 31, 2017 12:48 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Farshad wrote:

Delphi does not Free nil objects.

Calling Free() on a nil object pointer is perfectly safe. But calling
Free() on a non-nil pointer that does not point at a valid object is not
safe.

The example you have shown claims the pointer is not nil when it is expected
to be nil. But when I test it in XE2, the exception bypasses the finally
block completely (the RTL ends up executing the 'try' block twice - creating
two exceptions objects - and then jumps into an UnhandledException handler
instead of the 'finally' block):

procedure Test;
var
  B : TObject;
begin
  B := nil;
  try
    Exit;
  finally
    try
      raise Exception.Create('Error Message'); // <-- executed twice
    finally
      B.Free; // <-- never reaches here!
    end;
  end;
end;


If in XE2 it never reaches B.Free it means that XE2 compiler is also buggy, but in a different manner.
Paulo França La...

Posts: 16
Registered: 1/26/17
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 31, 2017 12:39 PM   in response to: Farshad Mohajeri in response to: Farshad Mohajeri
Farshad Mohajeri wrote:
Yes. Delphi does not Free nil objects.
Yes, you're right.

I confirm the issue occurs in Delphi 10.1 on Win7/64. Debugging shows the object assumes a random addr exactly after the second "try" runs, ie, right before the "raise" statement.
procedure TFormBug.Button1Click(Sender: TObject);
var
  B : TObject;
begin
  B := nil;
  try
    Exit;
  finally
    try  // B becomes <> nil after this.
      raise Exception.Create('Error Message');
    finally
      B.Free;
    end;
  end;
end;
Farshad Mohajeri

Posts: 120
Registered: 12/28/06
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 31, 2017 1:00 PM   in response to: Farshad Mohajeri in response to: Farshad Mohajeri
Generated code also looks weird. There are repeated blocks:

MainBug.pas.29: begin
00000000006ACED0 55               push rbp
00000000006ACED1 4883EC40         sub rsp,$40
00000000006ACED5 488BEC           mov rbp,rsp
00000000006ACED8 48896D28         mov [rbp+$28],rbp
00000000006ACEDC 48894D50         mov [rbp+$50],rcx
00000000006ACEE0 48895558         mov [rbp+$58],rdx
MainBug.pas.30: B := nil;
00000000006ACEE4 48C7453800000000 mov qword ptr [rbp+$38],$0000000000000000
MainBug.pas.32: try
00000000006ACEEC 90               nop
00000000006ACEED EB2D             jmp TFormBug.Button1Click + $4C
MainBug.pas.33: Exit;
00000000006ACEEF EB2B             jmp TFormBug.Button1Click + $4C
MainBug.pas.37: try
00000000006ACEF1 90               nop
00000000006ACEF2 90               nop
MainBug.pas.38: raise Exception.Create('Error Message');
00000000006ACEF3 488B0D9EB1D7FF   mov rcx,[rel $ffd7b19e]
00000000006ACEFA B201             mov dl,$01
00000000006ACEFC 4C8D0595000000   lea r8,[rel $00000095]
00000000006ACF03 E8C8E5D8FF       call Exception.Create
00000000006ACF08 4889C1           mov rcx,rax
00000000006ACF0B E87019D6FF       call @RaiseExcept
MainBug.pas.40: B.Free; // Exception occurs here. Normally B must be nil, but somehow it contains random bits!
00000000006ACF10 90               nop
00000000006ACF11 488B4D38         mov rcx,[rbp+$38]
00000000006ACF15 E8E6F3D5FF       call TObject.Free
00000000006ACF1A EB0C             jmp TFormBug.Button1Click + $58
00000000006ACF1C 4833C9           xor rcx,rcx
00000000006ACF1F 488B5528         mov rdx,[rbp+$28]
00000000006ACF23 E828000000       call TFormBug.Button1Click + $80
MainBug.pas.45: end;
00000000006ACF28 488D6540         lea rsp,[rbp+$40]
00000000006ACF2C 5D               pop rbp
00000000006ACF2D C3               ret
00000000006ACF2E 4890             xchg rax,rax
MainBug.pas.40: B.Free; // Exception occurs here. Normally B must be nil, but somehow it contains random bits!
00000000006ACF30 55               push rbp
00000000006ACF31 4883EC20         sub rsp,$20
00000000006ACF35 488BEC           mov rbp,rsp
00000000006ACF38 488B4A38         mov rcx,[rdx+$38]
00000000006ACF3C E8BFF3D5FF       call TObject.Free
00000000006ACF41 488D6520         lea rsp,[rbp+$20]
00000000006ACF45 5D               pop rbp
00000000006ACF46 C3               ret
00000000006ACF47 90               nop
00000000006ACF48 488D040500000000 lea rax,[rax+$0000]
MainBug.pas.37: try
00000000006ACF50 55               push rbp
00000000006ACF51 4883EC30         sub rsp,$30
00000000006ACF55 488BEC           mov rbp,rsp
00000000006ACF58 90               nop
MainBug.pas.38: raise Exception.Create('Error Message');
00000000006ACF59 488B0D38B1D7FF   mov rcx,[rel $ffd7b138]
00000000006ACF60 B201             mov dl,$01
00000000006ACF62 4C8D052F000000   lea r8,[rel $0000002f]
00000000006ACF69 E862E5D8FF       call Exception.Create
00000000006ACF6E 4889C1           mov rcx,rax
00000000006ACF71 E80A19D6FF       call @RaiseExcept
MainBug.pas.40: B.Free; // Exception occurs here. Normally B must be nil, but somehow it contains random bits!
00000000006ACF76 90               nop
00000000006ACF77 488B4528         mov rax,[rbp+$28]
00000000006ACF7B 488B4838         mov rcx,[rax+$38]
00000000006ACF7F E87CF3D5FF       call TObject.Free
00000000006ACF84 488D6530         lea rsp,[rbp+$30]
00000000006ACF88 5D               pop rbp
00000000006ACF89 C3               ret


In below code some registers seems to be not preserved properly:

MainBug.pas.37: try
00000000006ACF50 55               push rbp
00000000006ACF51 4883EC30         sub rsp,$30
00000000006ACF55 488BEC           mov rbp,rsp
00000000006ACF58 90               nop
MainBug.pas.38: raise Exception.Create('Error Message');
00000000006ACF59 488B0D38B1D7FF   mov rcx,[rel $ffd7b138]
00000000006ACF60 B201             mov dl,$01
00000000006ACF62 4C8D052F000000   lea r8,[rel $0000002f]
00000000006ACF69 E862E5D8FF       call Exception.Create
00000000006ACF6E 4889C1           mov rcx,rax
00000000006ACF71 E80A19D6FF       call @RaiseExcept
MainBug.pas.40: B.Free; // Exception occurs here. Normally B must be nil, but somehow it contains random bits!
00000000006ACF76 90               nop
00000000006ACF77 488B4528         mov rax,[rbp+$28]
00000000006ACF7B 488B4838         mov rcx,[rax+$38]
00000000006ACF7F E87CF3D5FF       call TObject.Free
00000000006ACF84 488D6530         lea rsp,[rbp+$30]
00000000006ACF88 5D               pop rbp
00000000006ACF89 C3               ret


Edited by: Farshad Mohajeri on Apr 1, 2017 9:54 AM
Arnaud Bouchez

Posts: 137
Registered: 8/2/15
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 2, 2017 3:41 AM   in response to: Farshad Mohajeri in response to: Farshad Mohajeri
I guess you have enough material here to fill an official QA request.
Farshad Mohajeri

Posts: 120
Registered: 12/28/06
Re: Critical Win64 compiler code generation bug.
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 2, 2017 8:59 AM   in response to: Arnaud Bouchez in response to: Arnaud Bouchez
Arnaud Bouchez wrote:
I guess you have enough material here to fill an official QA request.

Already filled.
Please check first post and vote for it.

--
Farshad Mohajeri
http://www.unigui.com
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02