Tekninen artikkeli

Hiljaisten stub-vikojen diagnosointi Pascal-PDF-kirjastossa

Kun Delphi-kirjasto kasvattaa käännöskonfiguraation ilman visuaalista kehystä, korvaavat luokat ovat se, jossa virheet asuvat. Ei alusta, ei kääntäjä: sijaiset. PDFlibPasilla on grafiikkakerros, joka tarjoaa bittikartan, piirtopinnan, fontin, metafilen ja tulostimen vastineet käännöksille ilman VCL:ää, ja sen porttaaminen Free Pascalille toi esiin jokaisen vikamuodon, jonka sijainen voi saada. Ne järjestäytyvät siististi diagnoosikustannuksen mukaan, ja järjestys on vastakohta sille, mitä intuitio ehdottaa

Sijainen, joka heittää, on halpa löytää; poikkeus nimeää metodin. Sijainen, joka palauttaa tyhjää dataa, on kallis, koska vika ilmenee useita kerroksia syystään etäämpänä. Sijainen, joka palauttaa menestyksen, on kaikkein pahin, koska paluukoodi on pätevä, virhekoodi on nolla, mitään poikkeusta ei heitetä, ja ainoa näyttö siitä, että jokin meni pieleen, on tulleissa tavuissa

Kolme hiljaisen stub-vikan muotoa Pascal-PDF-kirjastossa järjestettynä diagnoosikustannuksen mukaan heittävästä stubista menestykseen tyhjän tulosteen yllä
Heittävä sijainen on halpa diagnosoida, tyhjä data on kallista, ja menestyksen palautus tyhjän artefaktin yllä on pahin löytää

Muoto kolme: pätevä kuvatunniste tyhjän XObjectin yllä

Vektori-metafile-muunnin oli tyhjä proseduurirunko ei-VCL-konfiguraatiossa. Kaikki sen yläpuolella jatkoi toimimista. EMF-tuonnin sisääntulopisteet ja piirtopinnankaappaussisääntulopiste ajoivat loppuun asti ja palauttivat laillisen kuvatunnisteen, jonka kutsuja sitten sijoitti sivulle. Tiedostoon päätyi lomake-XObject, jonka sisältöpituus oli nolla. Sivu renderöityi valkoisena

Mikään ei raportoinut ongelmaa, ja siihen lukeutuu kirjaston oma demo-ohjelma tästä ominaisuudesta, joka piirsi tyhjän sivun eikä huomannut. Ei ollut epäonnistuvaa paluuarvoa tarkistettavana, koska kutsujono todella kaikki onnistuivat; ainoa väärässä ollut asia oli tuotetun virran koko. Tämän virheluokan diagnosointi tarkoittaa eri kysymyksen esittämistä: ei ”epäonnistuiko kutsu” vaan ”onko artefakti uskottava”. Nollan pituinen lomake-XObject, nollan pikselin kuva, nollan sisältötavun sivu – nämä ovat assertiot, jotka nappaavat sen

Korjauksella on kaksi puoliskoa, ja toinen puolisko on helppo unohtaa. Ensiksi, tee tyhjästä toteutuksesta heittävä, jotta vialla on kanava ylipäätään. Toiseksi, muunna tuo poikkeus null-tulokseksi kuvatehtaassa ja lisää null-tarkistukset kahteen paikkaan, jotka kuluttavat kuvatunnisteen, koska muuten ”siisti vika” muuttuu suoraan pääsyrikkomukseksi, kun sivupuu dereferoi ei mitään. Heittävä stub on parannus vain, jos kutsujat olivat valmistautuneita vikaan, jota he eivät olleet aiemmin voineet vastaanottaa

Muoto kaksi: tyhjää dataa, kolme kerrosta kaatumisesta

Metafile-piirtopinnan sijainen ei täyttänyt fyysisiä ulottuvuuksiaan. Tuo arvo jakautuu sivugeometrian laskelmaan, joten laskelma tuotti nollan, joten rajauslaatikon laskenta jakoi nollalla. Alaston poikkeuskäsittelijä nielaisi sen, kuvatehdas palautti null-tuloksen, ja pääsyrikkomus tapahtui lopuksi sivupuussa, kun nullia käytettiin. Kolme kerrosta syyn ja oireen välillä, poikkeuskäsittelijä keskellä pyyhkimässä näyttöä

Tyhjän metafile-piirtopintasijaisen vikaketju PDFlibPasissa päätyen pääsyrikkomukseen kolme kerrosta nollalla jaon jälkeen
Tyhjä ulottuvuus jakaa geometrialaskelman nollaan, alaston käsittelijä pyyhkii poikkeuksen, ja null-tunniste kaataa sivupuun

Samalla yksiköllä oli kaksi muuta esiintymää samasta mallista. Fonttiluokalla oli tyhjät Assign- ja konstruktorirungot, mikä merkitsee enemmän kuin näyttää, koska piirtopinnan fonttiominaisuus on vain luku -tyyppinen: siihen sijoittaminen on ainoa tapa toimittaa fontti, joten tyhjä toteutus tekee fontin valinnan hiljaisesti tehottomaksi ja teksti tulee ulos siinä, mikä oletus oli. Ja nollan pikseliä tuumaa kohti -arvo sai jokaisen piirtopintaa fonttimetriikoista koon kutsujan tuottamaan nolla kertaa nolla -piirtopinnan, mikä antaa tyhjän sivun ja menestyksen palautuksen

// Muoto, jota etsitään sijaisyksiköstä: metodi, joka ei tee mitään
// eikä heitä. Molemmat kääntyvät ja molemmat tuottavat ”menestyksen”
// ilman tulostetta
procedure TMetafileCanvasStandIn.Create(...);
begin
  // ei inherited-kutsua, ei kenttäalustusta
end;

function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
  Result := True;    // ja bittikartta on yhä tyhjä
end;

Leveä rakenne, joka säilyttää vain ensimmäisen merkin

Tämä ei ole ylipäätään sijaisongelma, mutta se kuuluu samaan luetteloon, koska oire on yhtä kaukana syystä. Tulostinluettelointirakenne julistettiin kaikkien kahdentoista string-jäsenensä olevan osoittimia yksitavuisiin merkkeihin, kun taas sitä täyttävä funktio on luettelointi-API:n leveämerkkinen variantti

Osoitinkoot ovat identtiset, joten rakenteen asettelu on oikein eikä mikään kaadu. Sen sijaan tapahtuu se, että UTF-16-merkkijonon lukeminen yksitavuisena merkkijonona pysähtyy ensimmäiseen nollatavuun, joka millä tahansa ASCII-tulostimella on toisen merkin ylempi puolikas. Jokainen tulostinnimi palasi täsmälleen yhtenä merkkinä. Alavirrassa nimivalidointi epäonnistui, tulostimen luonti epäonnistui ja tulostaminen epäonnistui jokaiselle koneen todelliselle tulostimelle, eikä mikään niistä oireista osoita rakennedeklaraatioon

UTF-16-tulostinnimi katkaistuna yhteen merkkiin sen jälkeen, kun leveä Win32-rakenne on julistettu PAnsiChar-jäsenillä PWideCharin sijaan
Yksitavuiset jäsenet lukevat UTF-16-nimen vain sen ensimmäiseen nollatavuun, joten jokainen tulostinnimi palaa täsmälleen yhtenä merkkinä
// Väärin: oikea koko, väärä elementtityyppi. Ei käännösvirhettä, ei kaatumista,
// jokainen merkkijono katkaistuna yhteen merkkiin
type
  TPrinterInfo2Wrong = record
    pServerName: PAnsiChar;
    pPrinterName: PAnsiChar;
    // ... kymmenen muuta
  end;

// Oikein: *W-rakenteella on leveät jäsenet läpi
type
  TPrinterInfo2W = record
    pServerName: PWideChar;
    pPrinterName: PWideChar;
    // ... kymmenen muuta
  end;

Siitä syntynyt sääntö on mekaaninen ja sen soveltaminen kannattaa ajattelematta: mille tahansa Win32-rakenteelle, jonka nimi päättyy W:hen, tarkista, että jokainen merkkijonojäsen on leveä variantti, kenttä kentältä. ANSI:n ja leveän maailman sekoittaminen ei tuota kääntäjädiagnostiikkaa eikä kaatumista, vain hiljaista katkaisua, ja sama pätee käänteisesti ANSI-variaanteihin

Alaston poikkeuskäsittelijä on todellinen vastustaja

Jokainen näistä tutkimuksista hidastui samalla rakenteella: käsittelijällä, joka nappaa kaiken ja muuntaa sen epätodeksi paluuarvoksi. Se on kohtuullinen asia kirjoittaa kuvadekooderin ympärille, koska vioittunut kuva ei saa kaataa dokumenttityötä. Se on myös laite sen ainoan tiedon poistamiseen, jota tarvitset

Käytännön vastaus on tehdä käsittelijästä väliaikaisesti äänekäs. Poikkeusluokan, viestin ja pinovedon vedostaminen alastoman käsittelijän sisältä debug-ehdollisen alla muuntaa selittämättömän null-paluun nimetyksi poikkeukseksi sijainnilla. Kahdessa kolmesta yllä olevasta tapauksesta se yksittäinen askel päätti tutkimisen, koska poikkeus oli nollalla jako tai pääsyrikkomus sijaismetodissa, jonka nimi sanoi kaiken

Tarkistusluettelo sijaispolun omaksumiseen

Neljä kohtaa siinä järjestyksessä, jossa ne kannattavat. Ennen kuin kutsut korvaavaan luokkaan, lue metodit, joita olet käyttämässä, ja varmista, että jokaisella on aito runko; tyhjä runko ei ole toteutuksen yksityiskohta, se on puuttuva ominaisuus. Suosi heittäviä sijaisia neutraaleja arvoja palauttavien sijaan, ja parita se null-tarkistuksilla niissä paikoissa, joissa tehdas voi nyt laillisesti palauttaa ei mitään. Varmista ominaisuus tutkimalla artefaktia, ei paluukoodia, koska koko vikamuoto tässä on siisti paluukoodi tyhjän artefaktin yllä; dokumentin todellisen sisällön tavutason erittely on nopein tapa nähdä se, ja artikkeli tiedostokoon auditoinnista kattaa kyseisen työkalut. Ja kun ominaisuudella ei ole toimivaa korvaavaa toteutusta, ohjaa kyseiset esimerkit polulle, joka toimii, ja sano miksi kommentissa, sen sijaan että jättäisit esittelyn, joka hiljaa tuottaa tyhjää tulostetta

Laajempi pointti pätee paljon kauemmas kuin yhteen kirjastoon. Mikä tahansa koodikanta, jossa on ehdollinen toinen toteutus, mock-kerros, headless-tila tai alustashim, on altis muodolle kolme. Syy siihen, että se piiloutuu niin hyvin, on se, että jokainen laatupiste, johon tiimi yleensä tukeutuu – paluukoodit, virhekoodit, poikkeukset, poistumistilat – on tilakanava, ja muoto kolme pitää ne kaikki puhtaina. Vain tuloste pettää sen. Tuo on myös perustelu sille, että artefakteja tarkistetaan tilojen sijaan käsiteltäessä luotamatonta syötettä, mistä kerrotaan artikkelissa luotamattoman PDF:n jäsentämisestä, ja sille, että renderöityä tulostetta verrataan moottorien välillä yhden luottamisen sijaan, mistä kerrotaan artikkelissa monimoottorinen renderöinti

PDFlibPas on natiivi Object Pascal -PDF-kirjasto Delphille, C++Builderille ja Free Pascalille, ja sen ei-VCL-konfiguraatio on se, mikä tekee headless- ja työkaluketjujen yli ulottuvista käännöksistä mahdollisia; nykyinen konfiguraatiokattavuus on lueteltu losLab PDF Developer Library -tuotesivulla