| Bug # | Delphi versions | Description |
| 110 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
Exception on converting IntToStr(Low(Integer)) back into an integer |
| 119 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
Passing an open array as a parameter to a constructor can damage the exception stack |
| 120 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
Delphi forgets to decrement the reference-count for long strings when assigning whole records |
| 121 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
ObjectRef IS ClassRef sometimes returns TRUE when ObjectRef = Nil. |
| 123 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
try..finally and try..except may generate bad code |
| 133 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
Bad code is generated for for loops starting at 1 and counting downto a value <=1 that is stored in a variable. |
| 135 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
"Pascal" calling convention gives trouble with "Object" types |
| 137 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
Range check error with subrange types in a for loop This is not a bug; this behaviour is documented |
| 140 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
A specific case where the optimizer produces bad code. |
| 144 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
Missed range check in Abs(smallint) |
| 146 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
D3 generates bad code for logical (bit-wise) operations |
| 151 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
A class with a dynamic constructor created through a class reference variable will cause an access violation. |
| 388 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
The code optimizer makes a serious mistake when optimizations are turned on and the body of a simple function is enclosed in a Try..Except block where the Except block includes a raise statement. |
| 418 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
Compiler generates invalid code when calling events returned by an indexed property. |
| 393 | 1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 |
optimiser Handling an exception trashes any local variable stored (by the optimiser) in EBX, ESI or EDI. |
Bug #110; last modified: before April 1998| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Gotcha | Gotcha | Gotcha | Gotcha | Gotcha | Gotcha | Gotcha | Gotcha |
s := IntToStr(Low(Integer)); i := StrToInt(s);Steve Schafer's answer was:
No, it's not a bug. Most compilers (not just Delphi) parse a string representing a negative integer as a negated positive integer. That is, the compiler sees -12345, so it evaluates 12345, and then negates it. This obviously won't work for the minimum integer, since there is no corresponding positive integer.However I still would say it is a bug. It doesn't help if other compilers have the same bug.
Bug #119; last modified: 12-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| N/A | Exists | Exists | Exists | Unknown | Fixed | Fixed | Fixed |
{$A+,B-,G+,H+,I+,J-,M-,O+,T-,U-,V+,W-,X+,Z1}
{$MINSTACKSIZE $00004000}
{$MAXSTACKSIZE $00100000}
{$IMAGEBASE $00400000}
{$APPTYPE Console}
{$WARNINGS On}
{$HINTS On}
{$DEFINE Bug}
{$IFDEF Bug}
{$DEFINE Fix1}
{$UNDEF Fix2}
{$ENDIF}
program Construct;
uses Windows, SysUtils;
type
TTest = class
constructor Create({$IFDEF Fix1} const {$ENDIF} T: array of Integer);
end;
constructor TTest.Create({$IFDEF Fix1} const {$ENDIF} T: array of Integer);
begin
inherited Create;
{$IFDEF Fix2}
asm
MOV EAX, [EBP+TYPE Pointer + TYPE Integer]
INC EAX
LEA ESP, [ESP+4*EAX]
end
{$ENDIF}
end;
var
Te: TObject;
begin
try
Te := TTest.Create([10,20]);
try
raise Exception.Create('BUG TEST');
finally
Te.Free
end
except
MessageBox(0,'Caught exception','Construction Error',MB_OK)
end
end.
Bug #120; last modified: before April 1998| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| N/A | Exists | Fixed | Fixed | Fixed | Fixed | Fixed | Fixed |
{$APPTYPE Console}
program BadRefs;
uses
SysUtils;
type
PInteger = ^Integer;
PLongString = ^TLongString;
TLongString = record
RefCount: Integer;
Length: Integer;
Data: array[0..1023] of Char
end;
type
rBug = record
TextStr : string
end;
procedure ShowRefCount(const s: string);
begin
Writeln( 'Ref count = ', PLongString(Integer(s)-2*SizeOf(Integer))^.RefCount )
end;
procedure xxx;
var
lProblem: rBug;
begin
lProblem.TextStr := lProblem.TextStr + 'Append';
ShowRefCount(lProblem.TextStr); // Reference count = 1
lProblem.TextStr := lProblem.TextStr;
ShowRefCount(lProblem.TextStr); // Reference count still = 1
lProblem := lProblem;
ShowRefCount(lProblem.TextStr) // Reference count now = 2 ! BUG!
end;
begin
xxx
end.
Finalize(lProblem2); lProblem2 := lProblem1;
Bug #121; last modified: 12-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Exists | Exists | Exists | Unknown | Unknown | Fixed | Fixed | Fixed |
{$APPTYPE Console}
program IsObjBug;
uses
SysUtils;
type
TBaseClass = class of TBaseObject;
TBaseObject = class
public
Field1: Integer;
end;
procedure CheckIsClass(UnknownObj: TBaseObject; UnknownClass: TBaseClass);
begin
if UnknownObj is UnknownClass then
Writeln( 'Object IS of this class.' )
else
Writeln( 'Object is NOT of this class.' ) // Delphi executes this line
end;
var
BaseObj: TBaseObject = Nil;
begin
{
Test the type of BaseObj using "is" ...
}
if BaseObj is TBaseObject then
Writeln( 'BaseObj is of type TBaseObj.' ) // Delphi executes this line
else
Writeln( 'BaseObj is NOT of type TBaseObj.' );
{
Test "is"-type using procedure ...
}
CheckIsClass(BaseObj,TBaseObject);
end.
A study of the assembler shows that the real bug here is that Delphi "optimises away" the first "is"-operation and hardcodes the True result. This isn't completely unreasonable since this operation basically reads
"if (Instance of TBaseObject) is TBaseObject then"
and the class-type of BaseObj is known at compile-time. However, this
does forget the case when the BaseObj variable has not yet been assigned.
Note: the bug also occurs with optimization off!
if Assigned(ObjectRef) then
Bug #123; last modified: before April 1998| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Unknown | Exists | Fixed | Fixed | Fixed | Fixed | Fixed | Fixed |
function Bug: Integer;
begin
try
Result := 42;
Exit;
finally
;
end;
Result := 0;
end;
Running this example reveals that this bug returns a garbage value. Looking at the code that Delphi 2 generates, one sees that the value of Result is not saved when exiting the try-finally block. This is a compiler bug.
A similar problem exists for the case where
try
Result := StrToInt('Hello') = 0;;
except
Result := false;
end;
try
Result := StrToInt('Hello') = 0;;
except
on Exception do
Result := false;
end;
Solves this particular problem.
Bug #133; last modified: before April 1998| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Unknown | Exists | Fixed | Fixed | Fixed | Fixed | Fixed | Fixed |
var
D, N: Integer;
begin
D := -5;
for N := 1 downto D do
Writeln (N);
Readln;
end.
What should happen is it counts from 1 down to -5. But instead, it is skipping
the last two counts, which is making it go to -3. Note that the -5 is an
example, any number <=1 will produce the same result. If you look at
generated code in assembly language you can see this is definately a code
generation bug..
Bug #135; last modified: 12-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Unknown | Gotcha | Fixed | Fixed | Fixed | Fixed | Fixed | Fixed |
In D2, the following sample code illustrates the problem:
type
PSomeObject=^TSomeObject;
TSomeObject=object
i: Integer;
constructor Init; pascal;
end;
var
SomeObject: PSomeObject;
When I declare the constructor with calling conv. pascal, calling
New(SomeObject,Init);doesn't reserve memory for SomeObject (its value will become nil) and the call to the constructor gives a GPF.
Bug #137; last modified: 23-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| N/A | N/A | N/A | N/A | N/A | N/A | Unknown | Unknown |
const
maxn = 30;
type
tindex = 1..maxn;
tarr = array[tindex] of integer;
procedure test(r: tindex);
var
k: tindex;
v1,v2: tarr;
i: integer;
begin
for k := 1 to r - 1
do begin
MessageDlg('Should not be here',mtError,[mbOk],0);
v1[k] := v2[k];
end;
end;
The problem disappears when I declare TIndex = 0..maxn.
Jim Berg pointed out that this behaviour is as documented:
"It was a bug when the code worked in version 1.02. If you look under
the for loop documentation it says that the final value expression must be
assignment compatible with the loop variable. 0 (zero) is not assignment
compatible with K, which has a type of tindex, thus the range
check error."
I (Reinier Sterkenburg) have now checked it also under Delphi 1 and there
the range check appears as well. So there's no bug at all.
Probably this bug report should be deleted from teh bug list altogether...
Bug #140; last modified: before April 1998| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| N/A | Exists | Fixed | Fixed | Fixed | Fixed | Fixed | Fixed |
procedure TForm1.Button1Click(Sender: TObject);
var
k : integer;
w : array[1..3] of integer;
label
2;
begin
for k:=1 to 3 do
w[k] := k;
k:=0;
FOR k:= 1 to 3 do begin
GOTO 2;
raise exception.create('Impossible!');
2: if k=2 then
showmessage(format('k, w[2], w[k] = %d, %d, %d (should all be
2)',[k,w[2],w[k]]));
end;
end;
Bug #144; last modified: 12-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Absent | Unknown | Exists | Unknown | Unknown | Fixed | Fixed | Fixed |
var
Big: longint;
Small: smallint;
begin
Big := low(smallint); { -32768 }
Small := low(smallint);
Big := abs(Big); { Big = 32768 }
Small := abs(Small) { Small = -32768 ! }
ShowMessage('big='+IntToStr(big)+' small='+IntToStr(small));
end;
Bug #146; last modified: 12-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Unknown | Unknown | Exists | Exists | Exists | Fixed | Fixed | Fixed |
program BitTest;
uses Windows;
var
intval: Integer;
begin
intval := 128+1;
if (intval and 128) > 0 then
MessageBox(0, 'We never get here!', 'Bug', MB_OK)
end.
According to the Object Pascal specification, Delphi should promote 128
and intval to the smallest integer-type that contains both their ranges.
In this case, since 128 is a Byte and intval is an Integer, and the Byte
range is entirely contained within an Integer, this logical AND should
be performed as an Integer. Instead, the following code is produced:
mov eax,81h test al,80h // The AND is being done as a Byte!! This is the bug.This code sets the sign flag, clears the overflow and zero flags, and so convinces the CPU that the result is negative. Therefore we never see the message box.
Note by Reinier Sterkenburg (12 Jul 98):
Is this really the same bug as the one Hector Santos complains about?
'His bug' depends on the optimizer setting; strange enough, the bug is only
present if optimization is off.
Bug #151; last modified: 12-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Unknown | Unknown | Exists | Unknown | Unknown | Fixed | Fixed | Fixed |
program BugMe;
uses Windows;
type
TBugClass = class of TBugObject;
TBugObject = class
constructor Create; dynamic;
end;
constructor TBugObject.Create;
begin
MessageBox(0, 'TBugObject.Create', 'Notice', MB_OK);
end;
var BugClass : TBugClass;
BugObject : TBugObject;
begin
BugClass := TBugObject;
{
Next Line assembles to:
mov dl, 01 // setup for constructor call
mov eax, [BugClass] // get class reference (VMT)
mov bx, FFFF // constructor's dynamic ID (-1)
call CallDynaInst // this is where the error occurs, should be CallDynaClass
}
BugObject := BugClass.Create;
MessageBox(0, 'After BugClass.Create', 'Notice', MB_OK);
end.
Further Description:
Bug #388; last modified: 12-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Unknown | Unknown | Unknown | Unknown | Exists | Fixed | Fixed | Fixed |
// Always returns 12345, yet appears to return -1 based on
// return value from the RetBadValue function.
function Returns12345: Integer;
begin
Result := 12345; // <-- Any value demonstrates the bug.
end;
// This function returns 0 instead of 12346 when optimizations
// are turned on!!! All other compiler flags make no difference
// as far as I can tell.
function RetBadValue: Integer;
var
ValueOf12345: Integer;
begin
try
ValueOf12345 := Returns12345;
Result := ValueOf12345 + 1;
except
raise;
end;
end;
// This version works because the local variable is not used.
function RetGoodValue1: Integer;
//var
// ValueOf12345: Integer; // <-- Leaving this in also works.
begin
try
Result := Returns12345 + 1; // <-- Not using local variable.
except
raise;
end;
end;
// This version works because the raise inside the Except block
// is removed. I can't even guess why.
function RetGoodValue2: Integer;
var
ValueOf12345: Integer;
begin
try
ValueOf12345 := Returns12345;
Result := ValueOf12345 + 1;
except
// raise; <-- Get rid of raise (Generates compiler warning)
end;
end;
// This version works because another function could get called
// from the normally error optimized function. This must force
// the code to use a stack variable instead of a register to
// store the return value?
function RetGoodValue3: Integer;
var
ValueOf12345: Integer;
begin
try
ValueOf12345 := Returns12345;
Result := ValueOf12345 + 1;
if Result = 0 then begin
// ShowMessage is never called because Result is
// never zero. Just threatening the call straightens
// out the optimizer.
ShowMessage('Returns12345 + 1 = 0');
end;
except
raise;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage('RetBadValue := ' + IntToStr(RetBadValue));
ShowMessage('RetGoodValue1 := ' + IntToStr(RetGoodValue1));
ShowMessage('RetGoodValue2 := ' + IntToStr(RetGoodValue2));
ShowMessage('RetGoodValue3 := ' + IntToStr(RetGoodValue3));
end;
Bug #418; last modified: 10-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| N/A | Exists | Exists | Exists | Exists | Fixed | Fixed | Fixed |
To reproduce the bug:
program TestBug;
uses
Windows,
SysUtils,
Classes;
type
TMethodArray = array[0..9] of TMethod;
TEventList = class
private
Count: integer;
FEvents: TMethodArray;
function GetItems(Index: integer): TMethod;
procedure HandleEvent(Sender: TObject);
public
constructor Create;
procedure NotifyOk(Sender: TObject);
procedure NotifyBug(Sender: TObject);
property Items[Index: integer]: TMethod read GetItems;
end;
constructor TEventList.Create;
var
i : integer;
begin
inherited Create;
Count := 10;
for i := 0 to Count-1 do
TNotifyEvent(FEvents[i]) := Self.HandleEvent;
end;
procedure TEventList.HandleEvent(Sender: TObject);
begin
MessageBeep(-1);
end;
function TEventList.GetItems(Index: integer): TMethod;
begin
Result := FEvents[Index];
end;
procedure TEventList.NotifyBug(Sender: TObject);
var
i : integer;
begin
for i := 0 to Count-1 do
TNotifyEvent(Items[i])(Sender); // << This generates invalid code
// that calls a "random" address
end;
procedure TEventList.NotifyOk(Sender: TObject);
var
i : integer;
Event: TNotifyEvent;
begin
for i := 0 to Count-1 do
begin
Event := TNotifyEvent(Items[i]);
Event(Sender);
end;
end;
var
EventList: TEventList;
begin
EventList := TEventList.Create;
try
EventList.NotifyOk(nil);
EventList.NotifyBug(nil);
finally
EventList.Free;
end;
end.
TNotifyEvent(Items[i])(Sender); // << This generates invalid codeThe workaround is to use a temporary variable to hold the result from the call to GetItems, then call the event through this variable as demonstrated in the NotifyOk method:
Event := TNotifyEvent(Items[i]);
Event(Sender);
Details: The invalid code generated for NotifyBug is:
testbug.46: TNotifyEvent(Items[i])(Sender); :0040A83F 8D4C2404 lea ecx,[esp+04] // ECX = @Temp (compiler adds extra var Temp: TMethod) :0040A843 8BD6 mov edx,esi // EDX = i :0040A845 8BC7 mov eax,edi // EAX = Self :0040A847 E8C8FFFFFF call testbug.TEventList.GetItems :0040A84C 8D6C2404 lea ebp,[esp+04] // EBP = @Temp :0040A850 8B1424 mov edx,[esp] // EDX = Sender :0040A853 8B4508 mov eax,[ebp+08] // EAX = ??? (Random) :0040A856 FF5504 call [ebp+04] // Call [Temp.Data]The two last instructions should have been compiled into:
:0040A853 8B4504 mov eax,[ebp+04] // EAX = Temp.Data :0040A856 FF5500 call [ebp] // Call [Temp.Code]Note that the offsets in the invalid code are off by 4 bytes. This causes the call to be made to the address of the event's object address (i.e. it tries to execute data).
testbug.56: Event := TNotifyEvent(Items[i]); :0040A87E 8D4C2408 lea ecx,[esp+08] // ECX = @Temp (compiler adds extra var Temp: TMethod) :0040A882 8BD6 mov edx,esi // EDX = i :0040A884 8BC7 mov eax,edi // EAX = Self :0040A886 E889FFFFFF call testbug.TEventList.GetItems :0040A88B 8B442408 mov eax,[esp+08] // :0040A88F 890424 mov [esp],eax // Event.Code := Temp.Code :0040A892 8B44240C mov eax,[esp+0C] :0040A896 89442404 mov [esp+04],eax // Event.Data := Temp.Data testbug.57: Event(Sender); :0040A89A 8BD5 mov edx,ebp // EDX = Sender :0040A89C 8B442404 mov eax,[esp+04] // EAX = Event.Data :0040A8A0 FF1424 call [esp] // Call [Event.Code]Issues:
However, if the construct is not supported, the compiler should flag this as a compile-time error. Currently it compiles without any warning and then crashes hard at run-time. This could potentially be a very hard bug to find an a given application, because normally you don't suspect the code generated by the compiler.
Rune Moberg added to this on 10 July 1998: The generated code by Delphi 4 is
mov eax,[ebp+04]
call [ebp]
and no access violations occur.
Bug #393; last modified: 12-Jul-98| 1.02 | 2.01 | 3.0 | 3.01 | 3.02 | 4.0 | 4.01 | 4.02 |
| Unknown | Exists | Exists | Exists | Unknown | Fixed | Fixed | Fixed |
{$APPTYPE Console}
program BadOptim;
uses
Windows, SysUtils;
{$R *.RES}
const MaxSayNo = 5;
{$O+}
procedure TestRaise;
var
WatchMe: Integer;
begin
WatchMe := 0;
while WatchMe < MaxSayNo do
begin
repeat
try
if MessageBox(0,'Raise an exception?',
'Bad Optimisation',MB_YESNO) = ID_YES then
raise Exception.Create('Exception raised!');
Break
except
{
Catch-all exception handler - swallows the exception whole,
as it should. Control now passes to the "until" statement.
WatchMe is stored in EBX by the optimiser - this should have
been OK, except that the internal Delphi system function
_RaiseExcept trashes EBX (and ESI, EDI) ... oops!
}
end
until False;
Inc(WatchMe)
end
end;
begin
TestRaise
end.
When you run this, you will be continually asked whether or not you wish to
raise an exception. A counter records the number of times you press "No",
and when you have said "No" enough times the program will end. If you
press "Yes" then Delphi will raise an exception, catch it immediately
and then keep on looping. However, with optimisation on, Delphi destroys
the WatchMe variable as soon as the exception is raised! This effectively
fills WatchMe with random garbage, i.e. a big number a lot greater than
MaxSayNo(=5), causing the outer loop to terminate immediately.
The upshot of this is that:
a) when compiled with optimisation ON, pressing "No" will always
terminate the application once you have raised at least one exception
b) when compiled with optimisation OFF, you must always press "No"
MaxSayNo times to terminate the application, no matter how many
exceptions you raise.
This obvious difference in behaviour proves that this is a bug.
{$O+}
procedure GenerateExceptions;
begin
repeat
try
if MessageBox(0,'Raise an exception?',
'Bad Optimisation',MB_YESNO) = ID_YES then
raise Exception.Create('Exception raised!');
Break
except
{
Catch-all exception handler - swallows the exception whole,
as it should. Control now passes to the "until" statement.
}
end
until False
end;
procedure TestRaise;
var
WatchMe: Integer;
begin
WatchMe := 0;
while WatchMe < MaxSayNo do
begin
GenerateExceptions;
Inc(WatchMe)
end
end;
I cannot think of a less drastic workaround for this problem.
Comments are welcome, but the only ones who can really solve this are Borland.