Sunday, September 14, 2008

Ошибка в Delphi RTL - функция GetPropValue неверно работает с со свойствами типа Cardinal

Обнаружил неприятную ошибку в Delphi RTL. Казалось бы, такой совсем безобидный код вызывает Range check error:
type
TFoo = class(TPersistent)
private
FBar: Cardinal;
published
property Bar: Cardinal read FBar write FBar;
end;
...
var
Obj: TFoo;
Value: Variant;
begin
Obj := TFoo.Create;
try
// Устанавливаем любое значение, выходящее за рамки
// типа Integer
Obj.Bar := 4294967295; {$FFFFFFFF}
Value := GetPropValue(Obj, 'Bar');
SetPropValue(Obj, 'Bar', Value); // Error
finally
Obj.Free;
end;
end;

Почему так происходит? Заглянув в исходный код модуля TypInfo.pas, обнаружим такие строки:
  case PropInfo^.PropType^^.Kind of
tkInteger, tkChar, tkWChar, tkClass:
Result := GetOrdProp(Instance, PropInfo);

Очевидно, наше свойство имеет Kind = tkInteger, следовательно, Result будет присвоен результат вызова функции GetOrdProp. Казалось бы, все логично, но посмотрев на прототип функции GetOrdProp обнаружим, что возвращаемое значение имеет тип Longint - т.е. 4 байтное целое со знаком. Следовательно, вариантной переменной Result изначально установится неверное значение поля VType. Оно станет равным varInteger ($0003), вместо varLongWord ($0013). Ошибка имеет место быть именно здесь. Значение Value будет интерпретироваться как -1, а не как 4294967295.
Следующая строка в нашем примере вызовет проявление этой ошибки. Заглянув в реализацию функции SetPropValue увидим, что перед установкой свойству значения, происходит проверка, не выходит ли оно за диапазон возможных для данного типа:
function RangedValue(const AMin, AMax: Int64): Int64;
begin
Result := Trunc(Value);
if (Result < AMin) or (Result > AMax) then
RangeError;
end;

Функция вызывается с верными параметрами - AMin = 0; AMax = 4294967295. Но Result в первой строке будет присвоено значение -1, т.е. наше $FFFFFFFF, которое мы установили свойству, но интерпретируемое как Integer, а не как Cardinal. И, разумеется, это значение не пройдет проверку на вхождение в диапазон.
Обходной путь для решения данной проблемы - не использовать Get(Set)PropValue для свойств, которые имеют или могут иметь тип 4-байтного целого без знака. Предусмотрите этот случай, и используйте вместо этого функции Get(Set)OrdProp.

6 comments:

Anonymous said...

Спасибо за предупреждение. Толко что проверил, в Delphi 2009 тот же самый эффект.

Anonymous said...

так делать неправильно. должно быть GetObjectProp.

Anonymous said...

ой сорри. прогнал.

Anonymous said...

Очень интересная статья вышла! Молодцом автор! :)

Anonymous said...

Узнал много. Спасибо

Anonymous said...

Теперь GetOrdProp возвращает NativeInt (смотрю на Delphi XE7)