This blog post is about a new discovery I have made in Delphi, and the way to circumvent it.
The Generics compiler bug
I had a weird issue in kbmMW SmartBinding\s DefineData feature, which is used as a way to live push external data into kbmMW’s binding mechanism, making it possible to update visual components on the fly based on external data.
The DefineData method exists in 4 different overloaded variations, 2 of generic types and 2 of non generic types.
function DefineData(const AName:string; const AData:IkbmMWBindingData):TValue; overload; function DefineData(const AName:string; const AData:TValue; const AManaged:boolean = false):TValue; overload; function DefineData<T:class>(const AName:string; const AData:T):TValue; overload; function DefineData<T>(const AName:string; const [ref] AData:T; const AAsReference:boolean):TValue; overload;
DefineData can be told to manage the data provided for it, meaning it is responsible for freeing the data when it is no longer used by the binding framework, which is a powerful way to leave the responsibility of the lifetime of the data to SmartBind….. if it only worked!
I discovered that despite my best efforts, the data were not automatically freed. I looked high and low to make sure that the intricate internal routines handling all this, actually worked, before I realized what the problem was.
First some test code showing the use of DefineData where I noticed it failing:
... TMyObject = class private public destructor Destroy; override; end; ... implementation ... destructor TMyObject.Destroy; begin inherited; end; procedure TForm6.FormCreate(Sender: TObject); var o:TMyObject; begin o:=TMyObject.Create; Binding.DefineData('object',o,true); o:=TMyObject.Create; Binding.DefineData('object',o,true); Binding.UndefineAllData; end;
Basically I create a simple object, define it to SmartBind with the argument true, meaning that SmartBind must take ownership of the object instance. Then I create a new one and define that under the same name as the previous one, which should result in the previous TMyObject should be freed, which did not happen.
Finally UndefineAllData removes all data definitions regardless of names. The second instance of TMyObject ought to disappear here, but alas it did not.
So OBVIOUSLY there must be a bug in DefineData and UndefineAllData. But no that is not the case.
The problem in this case is that the compiler chooses the wrong overloaded method when it compiles the code.
Stepping into the code, I realised that I stepped into the one with this signature:
function DefineData<T>(const AName:string; const [ref] AData:T; const AAsReference:boolean):TValue;
Which you can see is a generic version, and thus should not be selected unless I had used the syntax:
It should have selected the one with the signature:
function DefineData(const AName:string; const AData:TValue; const AManaged:boolean = false):TValue;
Impressively the compiler actually figured out (by the argument given for AData) that the generic type of DefineData should be DefineData<TMyObject>. That is impressive, but absolutely not intended. The compiler should never deduce the generic type based on the type of a T argument, but only on what has been given as the type between the generics brackets <>.
The only way to circumvent this bug is to make sure that there are no two methods (generics or not) that have potentially compatible argument lists while having the same method name.
Usually the compiler would issue a compile error, in case multiple overloaded methods matched the parameter list, but not in this case, which is not a bug, but correct behaviour. The bug is that it selects the wrong one.
As it is calling an incorrect method, it is a rather serious bug, which needs to be fixed.
I have for the moment only tested this issue in 10.3.