Reputation: 1695
I got switched to other project at work and I noticed that Delphi XE2 debugger does not show the line that raised exception. When i got back at home i started to investigate. Then I found out that it can be disabled in Tools -> Options -> Debugger options and check Integrated debugging. Also I unchecked everything under Language exceptions in Exception types to ignore list. Notify on Language Exceptions left checked. Project -> Options -> Compiling, I have defaults there and Overflow and Range cheking enabled. I am running Debug build. I Cleaned it.
I have not noticed before, but now Delphi debugger doesn't give me the line when I call this code:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
_List: TStringList;
begin
_List := TStringList.Create;
try
Caption := _List[0]; // 'List index out of bounds (0)' here
finally
FreeAndNil(_List);
end;
end;
but this works (provided only to show that debugger does show the line for some things):
{$R+} // Range check is ON
procedure TForm1.BitBtn2Click(Sender: TObject);
var
_myArray: array [1 .. 5] of string;
i: integer;
begin
for i := 0 to 5 do
begin
_myArray[i] := 'Element ' + IntToStr(i); // Range check error here
ShowMessage('myArray[' + IntToStr(i) + '] = ' + _myArray[i]);
end;
end;
What is happening here? How to make the debugger to show as much as possible?
Thank you.
Upvotes: 3
Views: 2955
Reputation: 76597
Let me answer the question first.
How to make the compiler show as much as possible
The compiler shows you that the error is in the call to the btnclick.
The trick is to put a breakpoint on the first line of the btnclick proc with F5.
Then rebuild (!) the application and run again.
The execution will stop that the breakpoint.
Step through the code using F8 until the error shows up.
Put a breakpoint F5 on the line that generated the error.
Abort and rerun the application.
When you get to the second breakpoint instead of pressing F8, press F7 to step inside the routine that causes the error, keep on pressing F7/F8 until you see what exactly is causing the problem.
Why is this happening?
The compiler traces back the source of the exception by following the stack trace.
Because in your case the code that generated the exception does not have stack trace (because it is not debug code), the compiler cannot do this trick and instead follows the stack trace that is does have; it moves one level up in your code and flags the exception there.
A look at this in detail
You're comparing apples and oranges.
This code (Exhibit A):
Caption := _List[0]; // 'List index out of bounds (0)' here
Has absolutely nothing in common with this code (Exhibit B):
_myArray: array [1 .. 5] of string;
....
_myArray[0]:= 'Hallo';
Exhibit A uses the TStringList class's items
property, which is defined more or less as follows (I've simplified it a bit, but the fundamentals are correct):
type
TStringList = class(TStrings)
strict private
FList: array of string;
....
private
procedure Put(index: integer; const value: string);
function Get(index: integer): string;
published
property Items[index: integer]: string read Get write Put; default;
// ------------------------------------------------------^^^^^^^
....
end;
Notice the default
keyword at the end of the Items
property.
What this all means is that when you call _List[0]
, you are really calling _List.Items[0]
, which gets translated into Caption:= _List.Getitems(0)
, because of the read modifier on the property.
The default
keyword allows you to omit the .Items
.
The Get
code looks like this:
function TStringList.Get(Index: Integer): string;
begin
if Cardinal(Index) >= Cardinal(FCount) then
Error(@SListIndexError, Index); <<-Here is the line that generates the error*
Result := FList[Index].FString;
end;
*Actually the error is generated inside the Error
routine
Unless you have the RTL/VCL source code and you're running with debug DCU's you will not get a break on the exact trigger of the exception (which is inside the system.classes) unit.
Note that this error does not depend on range checking, it will always fire.
Because Delphi does not have debug info for the exact line where the error is generated, it does the next best thing and tries to make a guess.
Short version
The stringlist is a complex abstraction pretending to be an array.
Lots of code gets called, making the pinpointing of the error difficult for the compiler.
In Exhibit B:
_myArray: array [1 .. 5] of string;
....
i:= 0;
_myArray[i]:= 'Hallo';
Either a range check error or an access violation is generated.
Both of these errors occur on the exact line, allowing the compiler to stop at the correct spot.
Short version
The plain array is a basic building block with no hidden calls to code elsewhere, making pinpointing errors very easy for the compiler.
Understanding properties
class and record properties (and now class operators) look like simple assignments/operations to/with variables, but are in fact calls to (possibly complex) subroutines.
Upvotes: 3