Это дефект компилятора, который влияет на дженерики.Время жизни экземпляра TMyClass
на самом деле не имеет значения.Код, который не может обработать компилятор, находится в TList<T>.DeleteRangeInternal
в Spring.Collections.Lists
.Этот код:
if doClear then
Changed(Default(T), caReseted);
Помните, что T
- это указатель на метод, то есть тип с двумя указателями.Как таковой он больше, чем регистр.Компилятор превращает вызов Changed
в следующее:
Spring.Collections.Lists.pas.641: Changed(Default(T), caReseted);
00504727 B105 mov cl,$05
00504729 33D2 xor edx,edx
0050472B 8B45FC mov eax,[ebp-$04]
0050472E 8B18 mov ebx,[eax]
00504730 FF5374 call dword ptr [ebx+$74]
Обратите внимание, что компилятор обнуляет только 4 байта, а затем передает эти четыре байта в Changed
.
Однако придругая сторона этого - реализация Changed
, чей код для доступа к item
, который он передал, выглядит следующим образом:
Spring.Collections.Base.pas.1583: fOnChanged.Invoke(Self, item, action);
00502E58 FF750C push dword ptr [ebp+$0c]
00502E5B FF7508 push dword ptr [ebp+$08]
00502E5E 8D55F0 lea edx,[ebp-$10]
00502E61 8B45FC mov eax,[ebp-$04]
00502E64 8B4024 mov eax,[eax+$24]
00502E67 8B08 mov ecx,[eax]
00502E69 FF513C call dword ptr [ecx+$3c]
Первые две строки кода asm читают указатель метода изстек.Таким образом, ABI для параметров указателя метода состоит в том, что они передаются в стеке.Это задокументировано следующим образом:
Указатель метода передается в стек как два 32-разрядных указателя.Указатель экземпляра помещается перед указателем метода, поэтому указатель метода занимает самый низкий адрес.
Вернуться к коду, вызвавшему эту функцию.Он передал аргумент в регистр.Это несоответствие является причиной исключения, которое на самом деле происходит намного позже.Но здесь все идет на юг.
Давайте посмотрим на обходной путь.Мы изменили код в TList<T>.DeleteRangeInternal
так, чтобы он был таким:
var
defaultItem: T;
....
if doClear then
begin
defaultItem := Default(T);
Changed(defaultItem, caReseted);
end;
Теперь сгенерированный код выглядит так:
Spring.Collections.Lists.pas.643: defaultItem := Default(T);
0050472B 33C0 xor eax,eax
0050472D 8945E0 mov [ebp-$20],eax
00504730 8945E4 mov [ebp-$1c],eax
Spring.Collections.Lists.pas.644: Changed(defaultItem, caReseted);
00504733 FF75E4 push dword ptr [ebp-$1c]
00504736 FF75E0 push dword ptr [ebp-$20]
00504739 B205 mov dl,$05
0050473B 8B45FC mov eax,[ebp-$04]
0050473E 8B08 mov ecx,[eax]
00504740 FF5174 call dword ptr [ecx+$74]
Обратите внимание, что этот временной код генерируется для обнуления обоих указателей вуказатель метода, а затем передать их через стек.Этот код вызова соответствует коду вызываемого абонента.И все хорошо.
Я отправлю этот обходной путь в мой личный репозиторий Spring4D, и Стефан объединит его с веткой исправлений 1.2.2 в главном репо.
Я отправил отчет об ошибке: RSP-20683 .