I have shown this to several colleagues and no one has an explanation. I ran into this by pure chance, as I thought I spotted a bug in our code, but was surprised to see that the code actually ran. Here is a simplified version. This has been done with XE-2.
Everyone I have spoke to so far, also expects that a NullReferenceException should be thrown.
TUnexplainable = class(TObject)
public
function Returns19: Integer;
end;
function TUnexplainable.Returns19: Integer;
begin
Result := 19;
end;
The following test should NEVER work, but it runs succesfully. Why is no NullReferenceException thrown????
procedure TTestCNCStep.ShouldNeverEverWorkV4;
var
Impossible: TUnexplainable;
Int1: Integer;
begin
Impossible := nil;
Int1 := Impossible.Returns19; // A Null Reference Exception should ocurr here!!! Instead the method Returns19 is actually invoked!!!!
Check(Int1 = 19);
end;
A non-static class method is compiled into a standalone function with a hidden Self
parameter. So, your code is essentially doing the following, from the compiler's perspective:
//function TUnexplainable.Returns19: Integer;
function TUnexplainable_Returns19(Self: TUnexplainable): Integer;
begin
Result := 19;
end;
//procedure TTestCNCStep.ShouldNeverEverWorkV4;
procedure TTestCNCStep_ShouldNeverEverWorkV4(Self: TTestCNCStep);
var
Impossible: TUnexplainable;
Int1: Integer;
begin
Impossible := nil;
Int1 := TUnexplainable_Returns19(Impossible);
Check(Int1 = 19);
end;
As you can see, Returns19()
is not referencing Self
for anything, so there is no reason for a nil pointer error to occur.
Change your code to do something with Self
, and then you will see the error you are expecting:
type
TUnexplainable = class(TObject)
public
Number: Integer;
function ReturnsNumber: Integer;
end;
function TUnexplainable.ReturnsNumber: Integer;
begin
Result := Number;
end;
procedure TTestCNCStep.ShouldNeverEverWorkV4;
var
Impossible: TUnexplainable;
Int1: Integer;
begin
Impossible := nil;
Int1 := Impossible.ReturnsNumber; // An EAccessViolation exception will now occur here!!!
end;
I have shown this to several colleagues and no one has an explanation.
I would be very worried working with a group of colleagues (assuming they are programmers) who do not understand the basics of how class methods and the Self
parameter actually work, and how calling a class method via a nil pointer can avoid a null pointer error. This is like Object-Oriented Programming 101 kind of stuff.
Your method never references the Self
pointer, so it happens to work.
Unlike C#, Delphi does not validate the Self
(this
in C#) pointer automatically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With