När ett Delphi-bibliotek får en byggkonfiguration utan det visuella ramverket är ersättningsklasserna där buggarna bor. Inte plattformen, inte kompilatorn: stand-ins. PDFlibPas har ett grafiklager som tillhandahåller bitmapp-, canvas-, teckensnitts-, metafile- och skrivarekvivalenter för byggen utan VCL, och att porta det till Free Pascal framlade vartdera felläge en stand-in kan ha. De sorterar snyggt efter diagnoskostnad, och ordningen är motsatsen till vad intuitionen antyder
En stand-in som kastar är billig att hitta; undantaget namnger metoden. En stand-in som returnerar tomma data är dyr, eftersom felet visar sig flera lager från sin orsak. En stand-in som returnerar framgång är sämst av alla, eftersom returkoden är giltig, felkoden är noll, inget undantag kastas och det enda beviset på att något gick fel finns i de byte som kom ut
Form tre: en giltig bildidentifierare över ett tomt XObject
Vektor-metafilekonverteraren var en tom procedurkropp i icke-VCL-konfigurationen. Allt ovanför den fortsatte fungera. EMF-importingångspunkterna och canvasfångstingångspunkten kördes till slutet och returnerade en laglig bildidentifierare, som anroparen sedan placerade på en sida. Det som hamnade i filen var ett form XObject med innehållslängden noll. Sidan renderades vit
Inget rapporterade ett problem, och det inkluderar bibliotekets eget demoprogram för denna funktion, som ritade en tom sida och inte märkte det. Det fanns inget misslyckat returvärde att kontrollera, för anropssekvensen lyckades genuint allihop; det enda som var fel var storleken på den producerade strömmen. Att diagnostisera denna klass av defekt betyder att ställa en annan fråga: inte ”misslyckades anropet” utan ”är artefakten plausibel”. Ett form XObject med längden noll, en bild med noll pixlar, en sida med noll innehållsbyte, det är de assertioner som fångar det
Fixen har två halvor och den andra halvan är lätt att glömma. Först, gör den tomma implementeringen kastande, så att felet överhuvudtaget har en kanal. Sedan, omvandla undantaget till ett nullresultat i bildfabriken och lägg till nullkontroller på de två platser som konsumerar en bildidentifierare, för annars blir ”ren misslyckan” rakt igenom en åtkomstöverträdelse när sidträdet derefererar ingenting. En stub som kastar är bara en förbättring om anroparna var förberedda på ett fel de tidigare aldrig hade kunnat ta emot
Form två: tomma data, tre lager från kraschen
Metafile-canvas-stand-inen fyllde inte i sina fysiska dimensioner. Det värdet går in i en sidogeometriberäkning, så beräkningen gav noll, och bestämningen av begränsningsrutan dividerade med noll. En naken undantagshanterare svalde det, bildfabriken returnerade ett nullresultat och åtkomstöverträdelsen inträffade till slut i sidträdet när null användes. Tre lager mellan orsak och symptom, med en undantagshanterare i mitten som raderar bevisen
Samma enhet hade ytterligare två instanser av mönstret. Teckensnittsklassen hade tomma Assign- och konstruktorkroppar, vilket betyder mer än det ser ut, för canvasens teckensnittsegenskap är skrivskyddad: att tilldela till den är enda sättet att leverera ett teckensnitt, så en tom implementering gör teckensnittsval tyst verkningslöst och texten kommer ut i vadhelst standarden var. Och ett pixels-per-inch-värde på noll fick varje anropare som dimensionerar en canvas från teckensnittsmått att producera en noll-går-på-noll-canvas, vilket ger en tom sida och en framgångsretur
// Formen att leta efter i en stand-in-enhet: en metod som varken
// kastar eller gör något. Båda dessa kompilerar och båda ger
// framgång utan någon utdata
procedure TMetafileCanvasStandIn.Create(...);
begin
// inget inherited-anrop, ingen fältinitialisering
end;
function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
Result := True; // och bitmappen är fortfarande tom
end;
Den breda strukturen som behåller bara första tecknet
Denna är inte alls ett stand-in-problem, men den hör hemma i samma katalog eftersom symptomet är lika långt från orsaken. Skrivarenumereringsstrukturen deklarerades med alla sina tolv strängmedlemmar typade som pekare till enbytestecken, medan funktionen som fyller den är wide-character-varianten av enumerations-API:et
Pekarstorlekar är identiska, så strukturlayouten är korrekt och inget kraschar. Det som händer i stället är att läsning av en UTF-16-sträng som en enbytessträng stannar vid den första nollbyten, vilket för valfritt ASCII-skrivarnamn är den höga halvan av det andra tecknet. Varje skrivarnamn kom tillbaka som exakt ett tecken. Nedströms misslyckades namnvalidering, skrivarskapande misslyckades och utskrift misslyckades för alla riktiga skrivare på maskinen, och inget av de symptomen pekar på en strukturdeklaration
// Fel: rätt storlek, fel elementtyp. Inget kompileringsfel, ingen
// krasch, varje sträng avkortad till ett tecken
type
TPrinterInfo2Wrong = record
pServerName: PAnsiChar;
pPrinterName: PAnsiChar;
// ... tio fler
end;
// Rätt: en *W-struktur har breda medlemmar genomgående
type
TPrinterInfo2W = record
pServerName: PWideChar;
pPrinterName: PWideChar;
// ... tio fler
end;
Regeln som kommer ur det är mekanisk och värd att tillämpa utan att tänka: för varje Win32-struktur vars namn slutar på W, verifiera att varje strängmedlem är den breda varianten, fält för fält. Att blanda ANSI- och bredvärldarna ger varken en kompilatordiagnostik eller en krasch, bara tyst avkortning, och samma sak gäller omvänt för ANSI-varianterna
En naken undantagshanterare är den verkliga motståndaren
Varenda en av dessa utredningar saktades ner av samma konstruktion: en hanterare som fångar allt och omvandlar det till ett falskt returvärde. Det är rimligt att skriva runt en bildavkodare, eftersom en korrupt bild inte ska fälla ett dokumentjobb. Det är också en anordning för att radera just den information du behöver
Det praktiska svaret är att göra hanteraren tillfälligt högljudd. Att dumpa undantagsklass, meddelande och backtrace inifrån den nakna hanteraren, under ett debugvillkor, omvandlar en oförklarlig nullretur till ett namngivet undantag med en plats. I två av de tre fallen ovan avslutade det enskilda steget utredningen, eftersom undantaget var en division med noll eller en åtkomstöverträdelse i en stand-in-metod vars namn sade allt
Checklista för att anta en stand-in-väg
Fyra punkter, i den ordning de lönar sig. Innan du anropar en ersättningsklass, läs de metoder du är på väg att använda och bekräfta att var och en har en verklig kropp; en tom kropp är inte en implementeringsdetalj, det är en saknad funktion. Föredra stand-ins som kastar framför stand-ins som returnerar neutrala värden, och para det med nullkontroller på platserna där en fabrik nu lagligen kan returnera ingenting. Verifiera en funktion genom att inspektera artefakten, inte returkoden, eftersom hela felläget här är en ren returkod över en tom artefakt; en bytegenomgång av vad ett dokument faktiskt innehåller är snabbaste sättet att se det, och artikeln om filstorleksgranskning täcker det verktyget. Och när en funktion saknar livsduglig ersättningsimplementering, dirigera de berörda exemplen till vägen som fungerar och säg varför i en kommentar, i stället för att lämna en demonstration som tyst ger tom utdata
Den bredare poängen gäller långt utöver ett bibliotek. Varje kodbas med en villkorlig andra implementering, ett mock-lager, ett headless-läge, en platformshim, är utsatt för form tre. Orsaken till att den gömmer sig så bra är att varje kvalitetsport ett team normalt litar på, returkoder, felkoder, undantag, exitstatusar, är en statuskanal, och form tre håller dem alla rena. Bara utdata förråder den. Det är också resonemanget bakom att kontrollera artefakter i stället för statusar vid hantering av obetrodd indata, beskrivet i artikeln om opålitlig PDF-parsning, och bakom att jämföra renderad utdata mellan motorer i stället för att lita på en, beskrivet i multimotorrendering
PDFlibPas är ett inbyggt Object Pascal-PDF-bibliotek för Delphi, C++Builder och Free Pascal, och dess icke-VCL-konfiguration är det som gör headless- och korsverktygskedjebyggen möjliga; aktuell konfigurationstäckning listas på produktsidan för losLab PDF Developer Library