Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TStrings is returned as "Inaccessible value" when exception is raised in function

Tags:

delphi

Could somebody help me explain why the TStrings is returned as "Inaccessible value" when an exception is raised in the underlyning function?

function GetStrings():TStrings;
begin
  result := TStringList.Create;
  try
    raise exception.Create('Error Message');
  except
    FreeAndNil(result);
    raise
  end;
end;

procedure TFormMain.Button1Click(Sender: TObject);
var
  S : TStrings;
begin
  try
    S := GetStrings;
    try
      //Do awesome stuff
    finally
      FreeAndNil(S)
    end;
  except
    //Debug watch: S = "Inaccessible value"
    Assert(S = nil, 'Assertion failure, S should be nil'); 
  end;
end;

The function GetStrings also returns "Inaccessible value" if I don't call FreeAndNil(result); prior to reraising the exception. However, then I have a memory leak on my hands aswell :-O

like image 307
Lars Avatar asked Aug 14 '14 14:08

Lars


1 Answers

try
  S := GetStrings; 
  try
    //Do awesome stuff
  finally
    FreeAndNil(S)
  end;
except
  //Debug watch: S = "Inaccessible value"
  Assert(S = nil, 'Assertion failure, S should be nil'); 
end;

Here is the sequence of execution:

  1. Since S is a local variable, its value is indeterminate until is has been initialized. That is the state as this code begins execution.
  2. The GetStrings function is called.
  3. An exception is raised in GetStrings. Which means that GetStrings does not return execution to the caller and so has the consequence that S is never assigned.
  4. The finally block does not execute because its try was never reached.
  5. Next, the except block executes, and S has indeterminate value, which is what you have observed.

In fact, the compiler should warn (W1036) that S might not have been initialized when you refer to it in the except block. But it doesn't seem to be able to do that, which is rather pathetic.

If you wish to be able to refer to S in your except block, you will need to nest it inside the try/finally.

S := GetStrings; 
try
  try
    //Do awesome stuff
  except
    //Do stuff with S
    Assert(S <> nil);
  end;
finally
  FreeAndNil(S)
end;

Another way of thinking about this is that S is only meaningful in between the code that assigns to S, and the code that destroys the object. The consequence being that the try/except must be nested inside the try/finally, if the except block is going to be able to refer to the object.

like image 81
David Heffernan Avatar answered Oct 08 '22 10:10

David Heffernan