Техническа статия

Диагностика на мълшиви заглушни откази в Pascal PDF

Когато Delphi библиотека придоби конфигурация за компилация без визуалния фреймуърк, заместващите класове са мястото, където живеят дефектите; Не платформата, не компилаторът: заместителите; PDFlibPas има graphics слой, който предоставя bitmap, canvas, font, metafile и printer еквиваленти за компилации без VCL, а пренасянето му към Free Pascal извади на повърхността всеки режим на отказ, който заместител може да има; Те се подреждат чисто по цена за диагностика, а подредбата е обратната на онова, което интуицията подсказва

Заместител, който хвърля, е евтин за откриване; изключението назовава метода; Заместител, който връща празни данни, е скъп, защото отказът се появява няколко слоя далеч от причината си; Заместител, който връща успех, е най-лошият от всички, защото return кодът е валиден, error кодът е нула, няма хвърлено изключение и единственото доказателство, че нещо се е объркало, е в байтовете, които излязоха

Три форми на мълшив отказ на заглушка в Pascal PDF библиотека, подредени по цена за диагностика, от хвърляща заглушка до успех върху празен изход
Хвърлящ заместител е евтин за диагностика, празните данни са скъпи, а успех върху празен артефакт е най-трудният за откриване

Форма три: валиден image идентификатор върху празен XObject

Векторният metafile конвертор беше празно тяло на процедура в non-VCL конфигурацията; Всичко над него продължи да работи; EMF импортните входни точки и входната точка за canvas прихващане тичаха до край и връщаха легален image идентификатор, който извикващият после поставяше на страница; Това, което попадна във файла, беше form XObject с дължина на съдържанието нула; Страницата се изобрази бяла

Нищо не докладва проблем и това включва собствената демонстрационна програма на библиотеката за тази функция, която нарисува празна страница и не забеляза; Нямаше неуспешна return стойност за проверка, защото последователността от извиквания наистина всички успяха; единственото грешно нещо беше размерът на произведената поток; Диагностицирането на този клас дефекти означава задаване на друг въпрос: не „успя ли извикването“, а „правдоподобен ли е артефактът“; Form XObject с дължина нула, изображение с нула пиксела, страница с нула байта съдържание — това са assertion-ите, които го хващат

Поправката има две половини и втората половина е лесна за забравяне; Първо, накарайте празната имплементация да хвърля, така че отказът изобщо да има канал; Второ, превърнете това изключение в null резултат на image factory-то и добавете null проверки на двете места, които консумират image идентификатор, защото иначе „чист отказ“ се превръща направо в access violation, докато page tree-то дереференцира нищо; Заглушка, която хвърля, е подобрение само ако извикващите са били подготвени за отказ, който никога преди не са могли да получат

Форма две: празни данни, три слоя от срива

Metafile canvas заместителят не попълваше физическите си размери; Тази стойност влиза в изчисление на геометрия на страница, така че изчислението произвежда нула, така че изчислението на bounding box раздели на нула; Гол обработчик на изключения погълна това, image factory-то върна null резултат и access violation накрая се случи в page tree-то, когато null беше използван; Три слоя между причина и симптом, с обработчик на изключения по средата, изтриващ доказателствата

Верига на отказ на празен metafile canvas заместител в PDFlibPas, стигаща до access violation три слоя след делението на нула
Празният размер дели геометрично изчисление до нула, гол обработчик изтрива изключението и null идентификаторът срива page tree-то

Същият модул имаше още два случая на образеца; Font класът имаше празни тела на Assign и конструктора, което има повече значение, отколкото изглежда, защото font свойството на canvas-а е read-only: присвояването в него е единственият начин да се достави шрифт, така че празна имплементация прави избора на шрифт мълшиво безействен и текстът излиза в каквото по подразбиране е било; И стойност pixels-per-inch нула накара всеки извикващ, който оразмерява canvas от font метрики, да произвежда нула на нула canvas, което дава празна страница и return успех

// Образецът, който да търсите в заместещ модул: метод, който нито
// хвърля, нито прави нещо. И двете се компилират и двете произвеждат
// „успех“ без изход
procedure TMetafileCanvasStandIn.Create(...);
begin
  // без inherited извикване, без инициализация на полета
end;

function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
  Result := True;    // а bitmap-ът все още е празен
end;

Широката структура, която запазва само първия символ

Този изобщо не е проблем на заместител, но принадлежи на същия каталог, защото симптомът е също толкова далеч от причината; Printer enumeration структурата беше декларирана с всичките ѝ дванайсет string членове типизирани като указатели към еднобайтови символи, докато функцията, която я попълва, е wide-character вариантът на enumeration API-то

Размерите на указателите са идентични, така че оформлението на структурата е правилно и нищо не се срива; Това, което се случва вместо това, е, че четенето на UTF-16 низ като еднобайтов низ спира на първия нулев байт, който за всяко ASCII принтерно име е старшата половина на втория символ; Всяко принтерно име се върна като точно един символ; Надолу по веригата валидацията на имена се провали, създаването на принтер се провали и печатането се провали за всеки реален принтер на машината и никой от тези симптоми не сочи към декларация на структура

UTF-16 принтерно име, съкратено до един символ, след като широка Win32 структура е декларирана с PAnsiChar членове вместо PWideChar
Еднобайтови членове четат UTF-16 име само до първия му нулев байт, така че всяко принтерно име се връща с точно един символ
// Грешно: правилен размер, грешен елементен тип. Няма компилаторна
// грешка, няма срив, всеки низ съкратен до един символ
type
  TPrinterInfo2Wrong = record
    pServerName: PAnsiChar;
    pPrinterName: PAnsiChar;
    // ... още десет
  end;

// Правилно: *W структура има wide членове навсякъде
type
  TPrinterInfo2W = record
    pServerName: PWideChar;
    pPrinterName: PWideChar;
    // ... още десет
  end;

Правилото, което излиза оттам, е механично и си заслужава да се прилага без мислене: за всяка Win32 структура, чието име завършва на W, проверете, че всеки string член е wide вариантът, поле по поле; Смесването на ANSI и wide световете не произвежда нито компилаторна диагноза, нито срив, само мълшиво съкращаване и същото важи обратно за ANSI вариантите

Гол обработчик на изключения е истинският противник

Всяко от тези разследвания беше забавяно от един и същ конструкт: обработчик, който хваща всичко и го превръща в false return стойност; Това е разумно нещо за писане около image decoder, тъй като повредено изображение не бива да събаря документна задача; То е и устройство за изтриване на единственото парче информация, от което се нуждаете

Практическият отговор е обработчикът временно да стане шумен; Извеждането на класа на изключението, съобщението и backtrace от вътрешността на голия обработчик, под debug условен израз, превръща необясним null return в назовано изключение с локация; В два от трите случая по-горе тази единствена стъпка прекрати разследването, защото изключението беше деление на нула или access violation в метод заместител, чието име казваше всичко

Контролен списък за възприемане на път с заместител

Четири точки, в реда, в който се изплащат; Преди да извикате заместещ клас, прочетете методите, които ще използвате, и потвърдете, че всеки има истинско тяло; празно тяло не е детайл на имплементацията, то е липсваща функция; Предпочитайте заместители, които хвърлят, пред заместители, които връщат неутрални стойности, и сдвоете това с null проверки на местата, където factory вече може законно да върне нищо; Верифицирайте функция, като инспектирате артефакта, не return кода, тъй като целият режим на отказ тук е чист return код върху празен артефакт; байтова разбивка на това, което документ действително съдържа, е най-бързият начин да го видите, а статията за одит на размера на файл разглежда тази инструментация; И когато функция няма жизнеспособна заместеща имплементация, насочете засегнатите примери към пътя, който работи, и кажете защо в коментар, вместо да оставите демонстрация, която мълшиво произвежда празен изход

По-широката точка се отнася далеч отвъд една библиотека; Всеки codebase с условна втора имплементация, mock слой, headless режим, platform shim, е изложен на форма три; Причината да се крие толкова добре е, че всяка качествена порта, на която един екип обикновено разчита — return кодове, error кодове, изключения, exit статуси — е статусен канал, а форма три ги пази всички чисти; Само изходът го издава; Това е и разсъждението зад проверката на артефакти, а не статуси, при обработка на ненадежден вход, описано в статията за парсване на ненадежден PDF, и зад сравняването на изобразения изход между engines, вместо да се вярва на един, описано в multi-engine изобразяване

PDFlibPas е родна Object Pascal PDF библиотека за Delphi, C++Builder и Free Pascal, а неговата non-VCL конфигурация е това, което прави headless и cross-toolchain компилации възможни; текущото покритие на конфигурации е изброено на продуктовата страница losLab PDF Developer Library