ÐÑеÑкиÑе пÑи пÑовеÑка на диапазона (range check errors) в Delphi PDF библиоÑекиÑе Ð¸Ð¼Ð°Ñ ÑепÑÑаÑиÑÑа на ÑÑÑдни за оÑкÑиване, ÑÑй каÑо не ÑÐ»ÐµÐ´Ð²Ð°Ñ Ð¿Ð¾ÑледоваÑелен модел на вÑзникване. Ðдин и ÑÑÑ Ð´Ð¾ÐºÑÐ¼ÐµÐ½Ñ Ð³Ð¸ пÑедизвиква на една маÑина, но не и на дÑÑга; ÑÑÑиÑÑ Ð¿ÑÑ Ð² кода задейÑÑва изклÑÑение пÑи Ñайл Ð¾Ñ 3 ÑÑÑаниÑи, но ÑабоÑи без пÑоблем пÑи Ñайл Ð¾Ñ 12 ÑÑÑаниÑи. Това неÑÑоÑвеÑÑÑвие поÑÑи винаги Ñе дÑлжи на една оÑновна пÑиÑина: обекÑиÑе на ÑÑÑаниÑиÑе в PDF Ñайла не Ñе ÑÑÑ ÑанÑÐ²Ð°Ñ Ð² Ñеда на ÑÑÑ Ð½Ð¾Ñо показване. Ðко библиоÑекаÑа изгÑажда ÑÐ²Ð¾Ñ Ð²ÑÑÑеÑен маÑив Ð¾Ñ ÑÑÑаниÑи ÑÑез поÑледоваÑелно ÑканиÑане на обекÑиÑе, вмеÑÑо да Ð¾Ð±Ñ Ð¾Ð¶Ð´Ð° дÑÑвоÑо Ð¾Ñ ÑÑÑаниÑи, деклаÑиÑано в каÑалога, ÑÑ ÐºÐ¾Ð½ÑÑÑÑиÑа индекÑ, ÑийÑо валиден диапазон не ÑÑоÑвеÑÑÑва на оÑакваниÑÑа, а пÑовеÑкаÑа на диапазона ÑÐ»Ð°Ð²Ñ Ñова неÑÑоÑвеÑÑÑвие в най-Ð½ÐµÐ¿Ð¾Ð´Ñ Ð¾Ð´ÑÑÐ¸Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ.
Ðак ÑабоÑи пÑовеÑкаÑа на диапазона в Delphi
ÐÑи акÑивна диÑекÑива на компилаÑоÑа {$R+} (коÑÑо е по подÑазбиÑане в конÑигÑÑаÑÐ¸Ñ Debug), Delphi RTL валидиÑа вÑеки Ð¸Ð½Ð´ÐµÐºÑ Ð½Ð° маÑив, подниз на низ и пÑиÑвоÑване на избÑоим Ñип по вÑеме на изпÑлнение. ÐоÑÑÑпÑÑ Ð¸Ð·Ð²Ñн гÑаниÑиÑе пÑедизвиква изклÑÑение ERangeError, вмеÑÑо ÑиÑ
омÑлком да ÑеÑе ÑÑÑедна памеÑ. Това поведение е изклÑÑиÑелно полезно: Ñо извежда наÑве ÑкÑиÑи гÑеÑки Ñано, вмеÑÑо да ги оÑÑави да повÑедÑÑ ÑÑÑÑкÑÑÑа Ð¾Ñ Ð´Ð°Ð½Ð½Ð¸, коÑÑо би Ñе ÑÑинала ÑÑо Ñеда по-надолÑ. ÐепÑиÑÑноÑо е, Ñе изклÑÑениеÑо Ñе задейÑÑва на мÑÑÑоÑо на доÑÑÑпа, а не Ñам, кÑдеÑо индекÑÑÑ Ðµ бил изÑиÑлен непÑавилно. ÐогаÑо ÑÑекÑÑ Ð½Ð° повикваниÑÑа (call stack) ÑоÑи кÑм дÑлбоко вложен меÑод в PDF модÑл, ÑеалнаÑа гÑеÑка обикновено Ñе намиÑа нÑколко нива по-нагоÑе.
СложниÑе логиÑеÑки ÑÑÐ»Ð¾Ð²Ð¸Ñ Ð´Ð¾Ð¿ÑлниÑелно ÑÑложнÑÐ²Ð°Ñ Ñова. Delphi оÑенÑва изÑазиÑе Ñ and Ð¾Ñ Ð»Ñво на дÑÑно ÑÑез ÑÑкÑаÑено оÑенÑване (short-circuit), но Ñо пÑопÑÑка изпÑлнениеÑо Ñамо когаÑо лÑваÑа ÑÑÑана е False. ÐзÑаз каÑо:
if FDocStarted and (DestIndex < Length(PageArr)) and
(PageArr[DestIndex].PageObj <> nil) then
изглежда безопаÑен, но Ñой пÑедпазва Ð¾Ñ Ð¸Ð½Ð´ÐµÐºÑ Ð¸Ð·Ð²Ñн гÑаниÑиÑе Ñамо ако FDocStarted е True и DestIndex е неоÑÑиÑаÑелно ÑиÑло. ÐÑовеÑкаÑа DestIndex < Length(PageArr) не вÑÑÑи ниÑо, когаÑо DestIndex е оÑÑиÑаÑелно ÑиÑло, ÑÑй каÑо ÑÑавнÑванеÑо на оÑÑиÑаÑелно ÑÑло ÑиÑло Ñ Ð½ÐµÐ¾ÑÑиÑаÑелна дÑлжина вÑÑÑа True в подпиÑанаÑа аÑиÑмеÑика (signed arithmetic) и поÑледваÑиÑÑ Ð´Ð¾ÑÑÑп до маÑива вÑе пак задейÑÑва гÑеÑка в диапазона. ÐзвежданеÑо на пÑовеÑкаÑа на гÑаниÑиÑе в най-вÑнÑнаÑа позиÑÐ¸Ñ Ðµ пÑавилноÑо ÑеÑение:
if (DestIndex >= 0) and (DestIndex < Length(PageArr)) then
begin
if FDocStarted and (PageArr[DestIndex].PageObj <> nil) then
Result := PageArr[DestIndex].PageObj
else
Result := nil;
end
else
raise ERangeError.CreateFmt(
'Page index %d is out of range (0..%d)',
[DestIndex, Length(PageArr) - 1]);
Това е меÑ
аниÑноÑо ÑеÑение. То ÑпиÑа ÑÑива на пÑиложениеÑо. Ðо Ñо не обÑÑнÑва заÑо пÑÑвонаÑално DestIndex е полÑÑил ÑÑойноÑÑ Ð¸Ð·Ð²Ñн Ð²Ð°Ð»Ð¸Ð´Ð½Ð¸Ñ Ð´Ð¸Ð°Ð¿Ð°Ð·Ð¾Ð½.
РеалнаÑа пÑиÑина: подÑедба на обекÑиÑе ÑÑеÑÑ Ð¿Ð¾Ð´Ñедба на ÑÑÑаниÑиÑе
ISO 32000-1 §7.7.3 деÑиниÑа дÑÑвоÑо Ð¾Ñ ÑÑÑаниÑи как дÑÑво Ð¾Ñ Ð²Ñзли Pages, ÑииÑо маÑиви Kids опиÑÐ²Ð°Ñ Ð¾Ð±ÐµÐºÑиÑе на ÑÑÑаниÑиÑе в Ñеда на ÑÑÑ
ноÑо показване. ФайлÑÑ ÑÑÑ
ÑанÑва Ñези обекÑи на пÑоизволни оÑмеÑÑваниÑ, избÑани Ð¾Ñ Ð·Ð°Ð¿Ð¸ÑваÑаÑа пÑогÑама; Ð¾Ð±ÐµÐºÑ Ð½Ð¾Ð¼ÐµÑ 20 може ÑизиÑеÑки да пÑедÑ
ожда Ð¾Ð±ÐµÐºÑ Ð½Ð¾Ð¼ÐµÑ 3 в байÑÐ¾Ð²Ð¸Ñ Ð¿Ð¾Ñок. ÐиблиоÑека, коÑÑо изгÑажда ÑÐ²Ð¾Ñ ÑпиÑÑк ÑÑÑ ÑÑÑаниÑи ÑÑез обÑ
ождане на ÑаблиÑаÑа Ñ Ð¿ÑепÑаÑки по номеÑа на обекÑиÑе, вмеÑÑо да Ñледва веÑигаÑа Kids, Ñе генеÑиÑа поÑледоваÑелноÑÑ, ÑазлиÑна Ð¾Ñ Ð¾ÑакванаÑа Ð¾Ñ Ð¿Ð¾ÑÑебиÑелÑ. ÐÑи докÑменÑи, пÑи коиÑо генеÑаÑоÑÑÑ Ðµ запиÑал ÑÑÑаниÑиÑе поÑледоваÑелно, вÑиÑко ÑабоÑи. Ðо пÑи Ñези, пÑи коиÑо Ñова не е Ñака, ÑазминаванеÑо Ð¼ÐµÐ¶Ð´Ñ Ð½Ð¾Ð¼ÐµÑаÑиÑÑа на ÑÑÑаниÑиÑе в библиоÑекаÑа и Ñази на извикваÑÐ¸Ñ ÐºÐ¾Ð´ води до индекÑи, коиÑо Ð¿Ð¾Ð¿Ð°Ð´Ð°Ñ Ð¸Ð·Ð²Ñн PageArr.
ÐÑавилниÑÑ Ð¿Ð¾Ð´Ñ
од изиÑква да Ñе запоÑне Ð¾Ñ ÐºÐ°Ñалога, да Ñе намеÑи коÑвенаÑа пÑепÑаÑка /Pages и маÑивÑÑ Kids да Ñе обÑ
оди ÑекÑÑÑивно. Ðа плоÑÑк докÑÐ¼ÐµÐ½Ñ Ð±ÐµÐ· междинни вÑзли Pages обÑ
ожданеÑо е ÑÑвÑем пÑоÑÑо:
procedure BuildPageIndexFromTree(
const KidsArray: THPDFArray;
var PageArr: TPageObjArray);
var
i, Idx: Integer;
Child: THPDFObject;
ChildType: string;
begin
for i := 0 to KidsArray.Count - 1 do
begin
Child := KidsArray.GetIndirectObject(i);
if Child = nil then
Continue;
ChildType := Child.GetNameValue('/Type');
if ChildType = 'Page' then
begin
Idx := Length(PageArr);
SetLength(PageArr, Idx + 1);
PageArr[Idx].PageObj := Child;
end
else if ChildType = 'Pages' then
begin
// intermediate node: recurse into its Kids
BuildPageIndexFromTree(Child.GetArray('/Kids'), PageArr);
end;
end;
end;
След изпÑлнениеÑо на Ñази пÑоÑедÑÑа PageArr[0] Ñе бÑде пÑÑваÑа ÑÑÑаниÑа, коÑÑо визÑализаÑоÑÑÑ Ñе покаже, незавиÑимо кÑде Ñе намиÑа Ñози Ð¾Ð±ÐµÐºÑ Ð² байÑÐ¾Ð²Ð¸Ñ Ð¿Ð¾Ñок. ÐндекÑиÑе, подадени Ð¾Ñ Ð¸Ð·Ð²Ð¸ÐºÐ²Ð°ÑÐ¸Ñ ÑоÑÑÑеÑ, коиÑо пÑÐ¸ÐµÐ¼Ð°Ñ Ñеда на показване, веÑе Ñе ÑÑпоÑÑавÑÑ Ð¿Ñавилно и гÑеÑкиÑе в диапазона изÑезваÑ.
ТвÑÑдо кодиÑаниÑе заобиколни ÑеÑÐµÐ½Ð¸Ñ Ð·Ð°Ð´ÑлбоÑÐ°Ð²Ð°Ñ Ð¿Ñоблема
Ркодови бази, кÑдеÑо оÑновнаÑа пÑиÑина никога не е била иденÑиÑиÑиÑана, ÑеÑÑо Ñе ÑÑеÑÐ°Ñ ÐµÐ²ÑиÑÑиÑни паÑове: ÑазмÑна на пÑÑваÑа и поÑледнаÑа ÑÑÑаниÑа, ако обÑиÑÑ Ð±Ñой е 3, ÑоÑаÑÐ¸Ñ Ð½Ð° индекÑа за докÑменÑи Ð¾Ñ ÑпеÑиÑиÑен генеÑаÑÐ¾Ñ Ð¸Ð»Ð¸ пÑилагане на оÑмеÑÑване, когаÑо пÑÑвиÑÑ Ð½Ð¾Ð¼ÐµÑ Ð½Ð° Ð¾Ð±ÐµÐºÑ Ð½Ð°Ð´Ð²Ð¸Ñи даден пÑаг. ÐÑеки Ð¾Ñ Ñези паÑове ÑÑоÑвеÑÑÑва ÑоÑно на набоÑа Ð¾Ñ ÑеÑÑови Ñайлове, Ñ ÐºÐ¾Ð¸Ñо Ñазполага ÑазÑабоÑÑикÑÑ Ð¿Ð¾ вÑеме на пиÑанеÑо мÑ. ÐобавÑнеÑо на ÑазлиÑен PDF изÑоÑник обаÑе води до задейÑÑване на нÑкой Ð¾Ñ Ð¿Ð°ÑовеÑе в гÑеÑен моменÑ, пÑавейки индекÑа двойно по-гÑеÑен: веднÑж заÑоÑо е изÑиÑлен Ð¾Ñ ÑазбÑÑкан маÑив, и вÑоÑи пÑÑ Ð·Ð°Ñади пÑиложеноÑо гÑеÑно пÑеобÑазÑване. ÐÑовеÑкаÑа на диапазона го ÑÐ»Ð°Ð²Ñ Ð½ÑкÑде по веÑигаÑа надолÑ, а ÑÑекÑÑ Ñ Ð³ÑеÑки не показва ниÑо полезно.
ÐдинÑÑвениÑÑ ÐµÑекÑивен пÑÑ Ðµ да Ñе пÑÐµÐ¼Ð°Ñ Ð½Ðµ вÑÑко евÑиÑÑиÑно пÑеобÑазÑване и да Ñе замени изгÑажданеÑо на маÑива ÑÑÑ ÑÑÑаниÑи Ñ ÐºÐ¾ÑекÑно Ð¾Ð±Ñ Ð¾Ð¶Ð´Ð°Ð½Ðµ на дÑÑвоÑо. ÐогаÑо индекÑиÑе Ñа пÑавилни по Ñождение, не Ñа Ð½ÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ð¸ никакви паÑове и пÑовеÑкаÑа на диапазона Ñе пÑевÑÑÑа в пÑедимÑÑво, а не в пÑеÑка.
Ðко поддÑÑжаÑе библиоÑека, коÑÑо показва Ñакова поведение, вклÑÑеÑе вÑеменно пÑовеÑкаÑа на диапазона в Release конÑигÑÑаÑÐ¸Ñ Ð¸ Ñ ÑеÑÑвайÑе Ñ ÑазнообÑазна база Ð¾Ñ PDF Ñайлове: докÑменÑи, ÑÑздадени Ð¾Ñ Word, LaTeX, ÑÑÑмÑÐµÑ Ð½Ð° ÑкенеÑи или инÑÑÑÑменÑи за ÑазделÑне на PDF. ФайловеÑе, коиÑо пÑÐµÐ´Ð¸Ð·Ð²Ð¸ÐºÐ²Ð°Ñ Ð¸Ð·ÐºÐ»ÑÑениÑ, Ñа именно Ñези, пÑи коиÑо ÑедÑÑ Ð½Ð° обекÑиÑе Ñе Ñазминава Ñ Ñеда на Ð¾Ð±Ñ Ð¾Ð¶Ð´Ð°Ð½Ðµ, койÑо ваÑиÑÑ ÐºÐ¾Ð´ пÑедполага. ÐÑеки ÑакÑв Ñайл е полезна оÑпÑавна ÑоÑка за анализ, а не оÑделен бÑг.
Ðа нов код, койÑо извиква Delphi PDF библиоÑека, пÑакÑиÑеÑкиÑÑ ÑÑÐ²ÐµÑ Ðµ да ÑазглеждаÑе бÑÐ¾Ñ ÑÑÑаниÑи в библиоÑекаÑа каÑо меÑодавен и никога да не подаваÑе индекÑ, изÑиÑлен вÑнÑно, без пÑÑво да поÑвÑÑдиÑе, Ñе Ñой попада в гÑаниÑиÑе 0..PageCount - 1. ÐомпоненÑÑÑ HotPDF пÑедоÑÑÐ°Ð²Ñ Ð¸Ð·ÑиÑÐ»ÐµÐ½Ð¸Ñ Ð±Ñой ÑÑÑаниÑи ÑÑез THotPDF.PageCount Ñлед BeginDoc или Ñлед заÑеждане на докÑменÑ; Ñази ÑÑойноÑÑ Ð²Ð¸Ð½Ð°Ð³Ð¸ оÑÑазÑва обÑ
ожданеÑо на дÑÑвоÑо и е безопаÑна за използване каÑо гоÑна гÑаниÑа пÑи изÑиÑлÑване на индекÑи.