You are not logged in.
Pages: 1
...or how i learned not to worry about type safety and hate the code generator.
PS: also featuring: different elements' types in static and dynamic arrays of the same element type
-----------
Working with legacy code i met a TFrame that used to intercept events of a dataset in another DataModule but did not nilled them on its destruction.
Wanted to make it safer and DRY i set to only change the whole list of event handlers in one procedure, and just call it twice.
...i thought that "procedure of object" is one of procedure types. Yet DCC XE2 has a varying opinions on the topic.
------------
type
_PEventNtf = ^TDataSetNotifyEvent; // iniially was an internal type inside the TFrame1, made it global - no difference
_PEventErr = ^TDataSetErrorEvent;
TFrame1 = class(TFrameBase)
....
type _EventsNtf = array [1 .. 10] of TDataSetNotifyEvent;
var dmDBXSaveNtf: _EventsNtf;
type _EventsErr = array [1 .. 3] of TDataSetErrorEvent;
var dmDBXSaveErr: _EventsErr;
// потом эти массивы одной функцией обмениваются с соотв. датасетами
// так что нельзя забыть восстановить то, что перехватывали
...
{$T+, D+} // $O- and $O+ generate the same code here
procedure TFrame1.Loaded;
var i_n, i_e: integer;
function Cell: _PEventNtf;
begin
Inc(i_n);
Result := @dmDBXSaveNtf[i_n]; // compiles and looks innocent...
end;
function Err: _PEventErr;
begin
Inc(i_e);
{$T-} // forcing to compile the next line: incorrect pascal sources generates the correct binary code
Result := @@dmDBXSaveErr[i_e];
end;
begin
inherited Loaded;
i_n := 0; i_e := 0;
Cell^ := quSQLQuery1AfterOpen;
Cell^ := quSQLQuery1AfterClose;
etc
And the 1st Cell^ := ... line suddenly fallen with attempt at writing @000004 :-[...]
It turned out, the function really did returned nil
CurrencyFrames.pas.648: begin // nested fn Cell
0AACE9F4 55 push ebp
0AACE9F5 8BEC mov ebp,esp
0AACE9F7 51 push ecx
CurrencyFrames.pas.649: Inc(i_n);
0AACE9F8 8B4508 mov eax,[ebp+$08]
0AACE9FB FF40FC inc dword ptr [eax-$04]
CurrencyFrames.pas.650: Result := @dmDBXSaveNtf[i_n];
0AACE9FE 8B4508 mov eax,[ebp+$08]
0AACEA01 8B40FC mov eax,[eax-$04]
0AACEA04 8B5508 mov edx,[ebp+$08]
0AACEA07 8B52F8 mov edx,[edx-$08]
0AACEA0A 8B84C208030000 mov eax,[edx+eax*8+$0308] // reading the value inside a cell instead of taking address
0AACEA11 8945FC mov [ebp-$04],eax
CurrencyFrames.pas.651: end;
0AACEA14 8B45FC mov eax,[ebp-$04]
0AACEA17 59 pop ecx
0AACEA18 5D pop ebp
0AACEA19 C3 ret
0AACEA1A 8BC0 mov eax,eax // gap filler
CurrencyFrames.pas.653: begin // nested fn Err
0AACEA1C 55 push ebp
0AACEA1D 8BEC mov ebp,esp
0AACEA1F 51 push ecx
CurrencyFrames.pas.654: Inc(i_e);
0AACEA20 8B4508 mov eax,[ebp+$08]
0AACEA23 FF40F4 inc dword ptr [eax-$0c]
CurrencyFrames.pas.656: Result := @@dmDBXSaveErr[i_e]; // line forced by {$T-}
0AACEA26 8B4508 mov eax,[ebp+$08]
0AACEA29 8B40F4 mov eax,[eax-$0c]
0AACEA2C 8B5508 mov edx,[ebp+$08]
0AACEA2F 8B52F8 mov edx,[edx-$08]
0AACEA32 8D84C258030000 lea eax,[edx+eax*8+$0358] // correctly taking the address of the array cell itself!
0AACEA39 8945FC mov [ebp-$04],eax
CurrencyFrames.pas.657: end;
0AACEA3C 8B45FC mov eax,[ebp-$04]
0AACEA3F 59 pop ecx
0AACEA40 5D pop ebp
0AACEA41 C3 ret
Last edited by Arioch (2014-02-06 17:17:45)
Offline
Trying to use Pointer Math instead
var p_n: _PEventNtf; p_e: _PEventErr;
function Cell: _PEventNtf;
begin
Result := p_n; // here we moved compiler scizoidness into the main fun body
Inc( p_n );
end;
function Err: _PEventErr;
begin
Result := p_e;
Inc( p_e );
end;
begin
inherited Loaded;
{$T+}
p_n := @dmDBXSaveNtf; // [DCC Error] E2010 Incompatible types: '_PEventNtf' and 'Pointer'
p_n := @@dmDBXSaveNtf; // [DCC Error] E2036 Variable required
p_e := @dmDBXSaveErr[1];
p_e := @@dmDBXSaveErr[1]; // [DCC Error] E2010 Incompatible types: '_PEventErr' and 'Pointer'
{$T-}
p_n := @dmDBXSaveNtf;
p_n := @@dmDBXSaveNtf; // [DCC Error] E2036 Variable required
p_e := @dmDBXSaveErr[1];
p_e := @@dmDBXSaveErr[1];
Cell^ := ...
So... is procedure of object variable even a procedure ???
Offline
Outcome of pointers math
{$T-}
p_e := @dmDBXSaveErr;
p_e := @@dmDBXSaveErr[1];
p_e := @dmDBXSaveErr[1];
{$T+}
p_e := @dmDBXSaveErr[1];
CurrencyFrames.pas.678: p_e := @dmDBXSaveErr; {$T-}
092FEA3D 8B45F4 mov eax,[ebp-$0c]
092FEA40 0560030000 add eax,$00000360 // Correct result, but type-unsafe
092FEA45 8945F8 mov [ebp-$08],eax
CurrencyFrames.pas.679: p_e := @@dmDBXSaveErr[1]; {$T-}
092FEA48 8B45F4 mov eax,[ebp-$0c]
092FEA4B 0560030000 add eax,$00000360 // Correct result, but type-unsafe
092FEA50 8945F8 mov [ebp-$08],eax
CurrencyFrames.pas.681: p_e := @dmDBXSaveErr[1]; {$T-}
092FEA53 8B45F4 mov eax,[ebp-$0c]
092FEA56 8B8060030000 mov eax,[eax+$00000360] // WRONG!!! Value instead of address! but "type-safe"
092FEA5C 8945F8 mov [ebp-$08],eax
CurrencyFrames.pas.683: p_e := @dmDBXSaveErr[1]; {$T+}
092FEA5F 8B45F4 mov eax,[ebp-$0c]
092FEA62 8B8060030000 mov eax,[eax+$00000360] // WRONG!!! Value instead of address! but "type-safe"
092FEA68 8945F8 mov [ebp-$08],eax
Then i tried to address Watches window - it failed to calculate values like @dmDbxSaveErr[0] and
@@dmDbxSaveErr[0]
I made one of the arrays dynamic - just to see if it counts - and suddenly Wathes started to work with both dynamic and static arrays
Offline
Oh, yes... about dynamic arrays... wpould they be any different from static ones ?
type _EventsErr = array { [1 .. 3] } of TDataSetErrorEvent; // dynamic - one more indirection level
SetLength(dmDBXSaveErr, 3);
{$T+}
p_e := @dmDBXSaveErr; // [DCC Error] E2010 Incompatible types: '_PEventErr' and 'Pointer'
{$T-} p_e := @dmDBXSaveErr;
{$T-} p_e := @dmDBXSaveErr[0];
{$T+} p_e := @dmDBXSaveErr[0];
{$T-} p_e := @@dmDBXSaveErr[0];
{$T+} p_e := @@dmDBXSaveErr[0];
CurrencyFrames.pas.679: {$T-} p_e := @dmDBXSaveErr;
094FEA7A 8B45F4 mov eax,[ebp-$0c]
094FEA7D 0560030000 add eax,$00000360
094FEA82 8945F8 mov [ebp-$08],eax
// Here we obtain the pointer to the DynArray controlling structure
// It is correct but is not what we want
// Just want to notice that DCC - both CodeGen and TypeChecker - worked coherently here
CurrencyFrames.pas.681: {$T-} p_e := @dmDBXSaveErr[0];
094FEA85 8B45F4 mov eax,[ebp-$0c]
094FEA88 8B8060030000 mov eax,[eax+$00000360] // here we obtain the cell address we crave for...
094FEA8E 8B00 mov eax,[eax] // and screw it, replacing with the VALUE of the cell instead
094FEA90 8945F8 mov [ebp-$08],eax
CurrencyFrames.pas.683: {$T+} p_e := @dmDBXSaveErr[0]; // same as above
094FEA93 8B45F4 mov eax,[ebp-$0c]
094FEA96 8B8060030000 mov eax,[eax+$00000360]
094FEA9C 8B00 mov eax,[eax]
094FEA9E 8945F8 mov [ebp-$08],eax
CurrencyFrames.pas.685: {$T-} p_e := @@dmDBXSaveErr[0];
094FEAA1 8B45F4 mov eax,[ebp-$0c]
094FEAA4 8B8060030000 mov eax,[eax+$00000360] // With dynamic array we have a correct working code...
094FEAAA 8945F8 mov [ebp-$08],eax
CurrencyFrames.pas.687: {$T+} p_e := @@dmDBXSaveErr[0];
094FEAAD 8B45F4 mov eax,[ebp-$0c]
094FEAB0 8B8060030000 mov eax,[eax+$00000360] // ...that even can be compiled without killing type safety!!!
094FEAB6 8945F8 mov [ebp-$08],eax
Offline
But how can it be that dynamic arrays would work correctly in Delphi ? I think we missed some details. What if we would just change the index?
SetLength(dmDBXSaveErr, 3);
{$T-} p_e := @dmDBXSaveErr;
{$T-} p_e := @dmDBXSaveErr[1];
{$T+} p_e := @dmDBXSaveErr[1];
{$T-} p_e := @@dmDBXSaveErr[1];
//remember? this compiles fine... or should i say "this at least compiles" ?
{$T+} p_e := @@dmDBXSaveErr[0];
// SUDDENLY!
{$T+} p_e := @@dmDBXSaveErr[1]; // [DCC Error] E2010 Incompatible types: '_PEventErr' and 'Pointer'
Type compatibility is different for 0th and 1st elements
CurrencyFrames.pas.681: {$T-} p_e := @dmDBXSaveErr[1];
0AB5EA85 8B45F4 mov eax,[ebp-$0c]
0AB5EA88 8B8060030000 mov eax,[eax+$00000360] // so we're fetching the address of the 0th element....
0AB5EA8E 8B4008 mov eax,[eax+$08] // and screw it overwriting it by 1st element value
0AB5EA91 8945F8 mov [ebp-$08],eax
CurrencyFrames.pas.683: {$T+} p_e := @dmDBXSaveErr[1]; // same as above
0AB5EA94 8B45F4 mov eax,[ebp-$0c]
0AB5EA97 8B8060030000 mov eax,[eax+$00000360]
0AB5EA9D 8B4008 mov eax,[eax+$08]
0AB5EAA0 8945F8 mov [ebp-$08],eax
CurrencyFrames.pas.685: {$T-} p_e := @@dmDBXSaveErr[1];
0AB5EAA3 8B45F4 mov eax,[ebp-$0c]
0AB5EAA6 8B8060030000 mov eax,[eax+$00000360] // so we're fetching the address of the 0th element....
0AB5EAAC 83C008 add eax,$08 // and just correctly shift it to the next element!
0AB5EAAF 8945F8 mov [ebp-$08],eax // but as usual, to get a correct valuewe had to kill type safety :-/
Offline
I think that avoiding type safety is not a thing for Pascal. So in my project i do the opposite and enforce T+ everywhere, like other safety net measures. And where i need to violate it, i do it by explicit typecasts or absolute vars.
But probably Delphi developers do not set T+ either, otherwise i wopuld not say how could they let that craze pass below radars
Offline
Most of our code uses explicit pointer types, with strong typing (e.g. for string/text process).
We try to do everything as explicit as possible, and rely on FastMM4 full debugg mode to ensure that we do not have any unexpected buffer overflows.
Offline
> code uses explicit pointer types,
It means nothing if they are not checked
> with strong typing
and you said you disabled typing check (as $T- }
Cannot link those two claims together
Last edited by Arioch (2014-02-07 16:34:27)
Offline
Pages: 1