Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is no NullReferenceException raised?

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;

Test resuts

like image 914
santiagoIT Avatar asked Apr 06 '15 21:04

santiagoIT


2 Answers

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.

like image 106
Remy Lebeau Avatar answered Sep 26 '22 08:09

Remy Lebeau


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.

like image 43
500 - Internal Server Error Avatar answered Sep 23 '22 08:09

500 - Internal Server Error