Nice bug that took me a lot of time to figure out :)
Nice bug that took me a lot of time to figure out :)
type
TTest = class
s: string;
constructor Create(s: string);
end;
constructor TTest.Create(s: string);
begin
Self.s := s;
end;
var
t: TTest;
procedure NiceBug(const s: string);
begin
// here RefCount(s) = 1 because of "const" keyword
t.Free;
// RefCount(s) = 0 !
// at this point s is no more allocated and should not be used anymore !
ShowMessage(s);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
t := TTest.Create('Hello');
NiceBug(t.s);
end;
type
TTest = class
s: string;
constructor Create(s: string);
end;
constructor TTest.Create(s: string);
begin
Self.s := s;
end;
var
t: TTest;
procedure NiceBug(const s: string);
begin
// here RefCount(s) = 1 because of "const" keyword
t.Free;
// RefCount(s) = 0 !
// at this point s is no more allocated and should not be used anymore !
ShowMessage(s);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
t := TTest.Create('Hello');
NiceBug(t.s);
end;
I bet FastMM fulldebug would have pointed you there pretty quick.
ReplyDeleteWell, Delphi pointed my an error in _UStrClr() but it's not easy to track in a real project. the string came from a object that request its own deletion by UID and the error occurs far away while building a JSON notification thrown over the network :/ And all this because the string is put in a Record that automatically tries to release this already released string.
ReplyDeleteNote that in the sample code above, there's no error at all, the string is deallocated but still in memory.
I looked into it and FastMM does not do what I thought. However I think you can hook UClrStr to mark the memory to easier spot strings that point to freed memory (just quick and dirty and tested in XE only):
ReplyDeleteunit UseStringAfterClearDetector;
interface
implementation
uses
DDetours,
Windows;
type
PStrRec = ^StrRec;
StrRec = packed record
codePage: Word;
elemSize: Word;
refCnt: Longint;
length: Longint;
end;
procedure MarkFreed(P: PStrRec);
var
i: Integer;
begin
for i := 0 to P.length - 1 do
PChar(PByte(P) + SizeOf(StrRec))[i] := 'X';
end;
function _UStrClr(var S): Pointer;
var
P: PStrRec;
begin
if Pointer(S) <> nil then
begin
P := Pointer(NativeInt(S) - SizeOf(StrRec));
Pointer(S) := nil;
if P.refCnt > 0 then
begin
if InterlockedDecrement(P.refCnt) = 0 then
begin
MarkFreed(P);
FreeMem(P);
end;
end;
end;
Result :=@S;
end;
var
p: Pointer;
function GetUStrClrAddr: Pointer;
asm
mov eax,offset System.@UStrClr
end;
initialization
p := InterceptCreate(GetUStrClrAddr,@_UStrClr);
finalization
InterceptRemove(p);
end.
yes but I already knows that it was freed, the question was how ? I don't know if FastMM is able to tell me that "s" was a member of an instance of TTest that was freed ;)
ReplyDelete