Abstrakt tolkning i statisk kodanalys

Abstrakt tolkning förklarad: Från gitterteori till infer och astrée

Betrakta två program som båda klarar varje test du skriver. Det ena är korrekt. Det andra har ett divideringsfel som bara utlöses när en specifik kombination av indata anländer samtidigt, en kombination som dina tester aldrig producerar. Traditionell testning kan inte avgöra vilken som är vilken. Abstrakt tolkning kan.

Abstrakt tolkning är det matematiska ramverk som ger statiska analysverktyg möjligheten att resonera kring alla möjliga beteenden hos ett program utan att köra det. Det är tekniken bakom Facebooks Infer-teknik för att hitta fel i nullpekare i stor skala, bakom Astrée-analysatorn som formellt verifierar Airbus flygkontrollprogramvara, och bakom varje statisk analysator som påstår sig vara sund, garantin att om ett program klarar analysen är det verkligen fritt från den typ av fel som kontrolleras. Att förstå hur det fungerar förklarar varför vissa verktyg hittar fel som andra missar, och varför dessa garantier kommer med specifika avvägningar.

Analysera kod utan att köra den

SMART TS XL tillämpar strukturell statisk analys på alla språk i din portfölj samtidigt.

Mer information

Vad är abstrakt tolkning?

Abstrakt tolkning är en teori om programapproximation, utvecklad av Patrick Cousot och Radhia Cousot år 1977. Kärnidén: istället för att beräkna den exakta mängden av alla möjliga programtillstånd, vilket i allmänhet är oavgörbart, beräkna en säker överapproximation med hjälp av en förenklad matematisk domän som är lätthanterlig att analysera.

Ordet ”abstrakt” betyder här inte vagt eller konceptuellt. Det hänvisar till en specifik matematisk operation: att abstrahera en uppsättning konkreta värden till en enklare representation som behåller de egenskaper du bryr dig om samtidigt som onödiga detaljer tas bort. Ett konkret heltal som 42 blir, i en teckenanalysabstraktion, helt enkelt "positiv". Abstraktionen förlorar information (du vet inte längre det exakta värdet) men vinner hanterbarhet (tecknet för ett heltal är en av tre möjligheter: positivt, negativt eller noll).

Det som gör detta användbart för programanalys är den garanti som följer med det: om analysen inte hittar några fel i den abstraherade domänen, finns det inget fel i någon konkret exekvering. Om den hittar ett potentiellt fel, kan det felet uppstå i praktiken, men inget verkligt fel kan döljas. Detta är sundhet.

Abstrakt tolkning kontra AST-analys kontra dynamisk analys

Dessa termer förväxlas ofta, ”AST-kodanalys” visas i sökdata för den här artikeln, och de beskriver olika saker.

Ett abstrakt syntaxträd är en datastruktur som representerar källkodens grammatiska struktur. Varje kompilator och linter bygger ett sådant. Det är grunden för parsning, refaktoreringsverktyg och mönsterbaserad statisk analys. AST-baserad analys hittar mönster: kod som matchar en regel (en funktion med för många parametrar, en SQL-sträng byggd genom sammankoppling) flaggas. Den resonerar inte om värden eller körningsbeteende.

Abstrakt tolkning resonerar om körningsbeteende utan att köra programmet. Den använder AST som indata men går långt utöver det: modellerar hur värden flödar genom programmet, vilka intervall variabler kan ta, om en pekare kan vara null vid en specifik anropsplats, om en loop avslutas. AST-analys är mönstermatchning. Abstrakt tolkning är beteendemässigt resonemang.

De flesta linters (ESLint, Checkstyle, Pylint) är främst AST-baserade. De flesta formella verifieringsverktyg (Infer, Astrée, Polyspace) använder abstrakt tolkning. Dynamisk analys (att köra programmet och observera faktiskt beteende) hittar endast buggar som utlöses av specifika indata. Abstrakt tolkning hittar buggar i alla möjliga indata utan att köra programmet alls.

De matematiska principerna bakom statisk analys

Frågan ”vilka är de matematiska principerna bakom statiska analysverktyg” visas direkt i sökdata. Här är det enkla svaret.

Abstrakt tolkning vilar på tre matematiska strukturer:

Gitter. Ett gitter är en delvis ordnad mängd där varje elementpar har en minsta övre gräns (förening) och en största undre gräns (möte). I statisk analys representerar gittret den abstrakta domänen, mängden möjliga abstrakta värden, ordnade efter hur mycket information de bär. För teckenanalys ser gittret ut så här:

        ⊤ (unknown -- could be anything)
       / \
   pos   neg
       \ /
        0
        |
        ⊥ (unreachable -- no possible value)

Att röra sig uppåt på gittret innebär att förlora precision (veta mindre). Att röra sig nedåt innebär att öka precisionen (veta mer). Det översta elementet ⊤ betyder "vi vet ingenting användbart". Det nedre elementet ⊥ betyder "detta tillstånd är ouppnåeligt".

Galoiskopplingar. En Galoiskoppling är det formella förhållandet mellan den konkreta domänen (faktiska programvärden) och den abstrakta domänen (den förenklade representationen). Den består av två funktioner: en abstraktionsfunktion α som mappar konkreta värden till deras abstrakta representation, och en konkretiseringsfunktion γ som mappar abstrakta värden tillbaka till den mängd konkreta värden de representerar.

Den kritiska egenskapen: den abstrakta domänen måste vara en säker överapproximation. γ(α(S)) ⊇ S för varje konkret mängd S. Abstraktionen kan inkludera fler värden än vad som faktiskt förekommer, det är det som producerar falska positiva resultat, men den får aldrig exkludera värden som faktiskt förekommer. Att exkludera verkliga värden skulle innebära att man missar verkliga buggar.

Fixpunktsiteration. För program med loopar måste analysen iterera tills den når ett stabilt tillstånd. För en loop som:

c

int x = 0;
while (condition) {
    x = x + 1;
}

På den första iterationen, x is {0}Efter en loopkropp, x skulle kunna vara {0, 1}Efter två, {0, 1, 2}Denna mängd fortsätter att växa, den stabiliseras aldrig av sig själv. Lösningen är vidgningen operator som tvingar fram konvergens genom att hoppa till en bredare approximation (vanligtvis [0, +∞) för intervallanalys). Analysen använder sedan förträngning för att återfå viss precision.

Denna beräkning med fast punkt är det som gör abstrakt tolkning komplett över alla exekveringsvägar, inklusive loopar, och det som gör den beräkningsmässigt dyrare än enkel mönstermatchning.

Abstrakta domäner: Att välja vad man ska approximera

Den abstrakta domänen avgör vad analysen kan och inte kan hitta. Olika domäner besvarar olika frågor om programbeteende.

Abstrakt domänVad det spårarExempel AnvändningVad den saknar
TeckenanalysOm värdena är positiva, negativa eller nollDivision med nolldetekteringExakta värden, överfyllnadsförhållanden
IntervallanalysÖvre och nedre gränser för numeriska värdenBuffertöverflöde, säkerhet för åtkomst till arrayerSamband mellan variabler
Octagon-domänLinjära samband mellan variablerMer exakt överfyllningsdetekteringIcke-linjära relationer
PekaranalysOm pekare kan vara null eller alias varandraNull-dereferens, använd-efter-frigöringObjektlivstid, heapform
Analys av smutsOm värden kommer från opålitliga källorSQL-injektion, XSS-detekteringImplicita flöden genom kontroll
Polyedrisk domänGodtyckliga linjära aritmetiska begränsningarLoopbunden verifieringPrestandakostnaden skalas exponentiellt

Avvägningen mellan domäner är alltid precision kontra prestanda. Intervalldomänen är snabb och fångar de flesta numeriska buggar. Den polyedriska domänen är mycket mer exakt men har exponentiell komplexitet i antalet variabler. Praktiska statiska analysverktyg väljer domäner som balanserar avvägningen för deras målapplikation, säkerhetskritiska inbyggda system har möjlighet till långsammare, mer exakt analys; CI/CD-integrerade linters behöver slutföras på sekunder.

Hur tre verkliga verktyg använder abstrakt tolkning

Snarare än att beskriva teorin isolerat, tydliggör konkreta verktyg dess tillämpning.

Facebook Infer använder en form av abstrakt tolkning som kallas bi-abduktion för att analysera Java, C, C++ och Objective-C för nullpekardereferenser, resursläckor och kappvillkor. Bi-abduktion upptäcker automatiskt förvillkor och eftervillkor för funktioner, vilket möjliggör interproceduranalys utan att manuella specifikationer krävs. Infer körs i CI hos Facebook, Spotify, Mozilla och dussintals andra stora organisationer eftersom det skalar till kodbaser på flera miljoner rader samtidigt som det förblir sundt för de felklasser det kontrollerar.

Astrée använder abstrakt tolkning med numeriska abstrakta domäner för att bevisa frånvaron av körtidsfel i C-program. Det användes av Airbus för att formellt verifiera den primära flygkontrollprogramvaran för A380, vilket bevisade frånvaron av körtidsfel i hela styrsystemet, en garanti som inget testprogram kunde ge. Astrée hittar inga falska negativa resultat för de felklasser som kontrolleras, även om det kan producera falska positiva resultat som kräver manuell granskning.

Polyspace (MathWorks) tillämpar abstrakt tolkning för inbäddad C- och C++-kod i säkerhetskritiska applikationer. Den klassificerar varje operation som "grön" (bevisligen inget fel), "röd" (definitivt ett fel) eller "orange" (potentiellt fel som kräver granskning). Den gröna klassificeringen är ett formellt bevis: ingen körning kan orsaka ett körtidsfel vid den operationen.

Triangeln Ljud-Precision-Prestanda

Verktyg för abstrakta tolkningar navigerar i en grundläggande triangel av konkurrerande egenskaper. Inget verktyg kan maximera alla tre samtidigt.

Sundhet innebär inga falska negativa resultat: varje riktig bugg i den analyserade klassen upptäcks. Sunda verktyg ger garantier; verktyg som inte är sunda kan missa buggar.

Precision innebär få falska positiva resultat: fynd motsvarar verkliga problem snarare än teoretiska problem som inte kan uppstå. Hög precision kräver mer förfinade abstrakta domäner och interprocedurell analys.

Prestanda innebär att analysen slutförs inom rimlig tid. Mer exakt analys är dyrare. Att bevisa frånvaron av alla körtidsfel i en kodbas med en miljon rader tar timmar; en linter-skanning tar sekunder.

Olika tillämpningar kräver olika punkter i denna triangel:

  • IDE-linting och CI/CDPrestanda först, precision sedan, sundhet valfritt
  • Säkerhetsskanningprecision först (minska utvecklarens trötthet), sundhet viktig för klasser med hög allvarlighetsgrad
  • Säkerhetskritisk certifiering: sundhet först (kan inte missa riktiga buggar), prestanda sekundär, falska positiva resultat acceptabla med manuell granskningsprocess

Abstrakt tolkning inom inbyggd och säkerhetskritisk utveckling

Frågan ”fördelar med statisk analys inom inbyggd utveckling” pekar på ett av abstrakt tolknings viktigaste tillämpningsområden. Inbyggda system, styrenheter för fordon, firmware för medicintekniska produkter och programvara för flygstyrning inom flygindustrin har begränsningar som gör abstrakt tolkning särskilt värdefull:

Ingen testkabel för alla tillstånd. En bilstyrenhet reagerar på tusentals sensorkombinationer i realtid. Att konstruera tester för varje kombination är omöjligt. Abstrakt tolkning täcker alla tillstånd samtidigt.

Certifieringskrav. DO-178C (flyg- och rymdteknik), ISO 26262 (fordon) och IEC 62443 (industriell kontroll) kräver att man visar att programvaran beter sig korrekt under alla förhållanden. Formell verifiering med hjälp av abstrakt tolkning kan uppfylla detta krav på ett sätt som testtäckningsrapporter inte kan.

Resursbegränsningar. Inbyggd programvara har ofta ingen minnesallokerare, ingen undantagshantering och ingen reservfunktion för operativsystemet. Ett runtime-fel, en nullpekare-dereferens, en array utanför gränserna, är ett hårt systemfel. Kostnaden för att missa dessa buggar är inte en kraschrapport och en snabbkorrigering. Det är en säkerhetsincident.

Astrée- och Polyspace-analysatorerna finns specifikt för detta sammanhang. Deras design accepterar höga falska positiva resultat och långsam analys i utbyte mot garantin att inga falska negativa resultat slinker igenom.

Falska positiva resultat och det utvidgande problemet

Den vanligaste kritiken mot abstrakta tolkningsverktyg är falska positiva resultat, varningar om potentiella fel som faktiskt inte kan uppstå i verklig utförande. Att förstå varför falska positiva resultat är inneboende, inte en kvalitetsbrist, gör dem lättare att hantera.

Falska positiva resultat uppstår från två källor:

Överapproximation i det abstrakta området. Om intervalldomänen spårar x ∈ [0, 100], den kan inte skilja mellan fall där x är alltid mindre än 50 i praktiken. En division med x kan flaggas som potentiellt dividerande med noll även när programlogiken garanterar x > 0En mer exakt domän (spårning av det exakta värdet, eller en begränsningslänkning x till en annan variabel) skulle eliminera det falskt positiva, men till högre beräkningskostnad.

Vidgning. Konvergensoperatorn som gör loopanalys hanterbar förlorar nödvändigtvis information. Efter breddning x från [0, 5] till [0, +∞), analysatorn vet inte längre att x förblir begränsad. Om koden kontrollerar assert(x < 1000) Efter loopen kan detta påstående inte längre bevisas, även om det är i praktiken x ligger alltid långt under 1000.

Praktiska strategier för att hantera falska positiva resultat: konfigurera analysen för att använda mer exakta domäner för kritiska moduler (med långsammare analys), undertryck bekräftade falska positiva resultat med riktade annoteringar och behandla verktygets orange/okända resultat som en prioriterad granskningskö snarare än bekräftade buggar.

Hur SMART TS XL Tillämpar statisk analys på företagsnivå

SMART TS XL verkar i det utrymme där abstrakt tolkningsteori möter företagsverklighet: kodbaser som spänner över flera språk, årtionden av utveckling och organisatoriska gränser som gör formell verifiering per program opraktisk.

Istället för att tillämpa en enda abstrakt domän på alla program, SMART TS XLÄr statisk kodanalys kombinerar strukturella analystekniker som är lämpliga för varje språk i miljön, COBOL, JCL, Java, Python, RPG, PL/I, SQL och moderna stackar, och producerar kvalitetsstatistik, beroendedata och säkerhetsresultat samtidigt över hela portföljen.

Funktionen för mappning av applikationsberoenden tillämpar grafteoretisk analys på den språköverskridande anropsgrafen och identifierar hur program, datamängder och jobbströmmar ansluter över språkgränser, den typ av helsystemanalys som enspråkiga verktyg inte kan utföra. Detta är strukturellt resonemang på systemnivå: inte att bevisa egenskaper hos enskilda program utan att bevisa egenskaper för hur de ansluter.

Effektanalysfunktionen tillämpar nåbarhetsanalys över beroendegrafen: givet en föreslagen förändring vid en nod, beräkna mängden av alla noder som är nåbara från den. Detta är den statiska analysfrågan "vad kommer att påverkas?" som besvaras utifrån kodens struktur snarare än från observationer vid körning eller mänsklig uppskattning.

För lag som genomför äldre modernisering program, SMART TS XLs strukturella analys överbryggar klyftan mellan formella abstrakta tolkningsverktyg (som är språkspecifika och kräver domänexpertis för att konfigureras) och det praktiska behovet av att förstå vad stora, odokumenterade, flerspråkiga äldre system faktiskt gör, en förutsättning för alla moderniseringsprogram som inte vill upptäcka sina dyraste överraskningar mitt i genomförandet.

Vanliga frågor om partihandel med mat och dryck

Vad är skillnaden mellan abstrakt tolkning och modellkontroll? Båda är formella metoder för programverifiering. Abstrakt tolkning överapproximerar uppsättningen möjliga tillstånd (bra men potentiellt oprecist). Modellkontroll utforskar uttömmande tillståndsrummet (komplett men endast genomförbart för ändliga, begränsade system). Abstrakt tolkning skalar till stora program. Modellkontroll skalar till komplexa egenskaper på mindre modeller. De kompletterar varandra, inte konkurrerar.

Är abstrakt tolkning endast för säkerhetskritisk programvara? Nej, även om den ger sitt tydligaste värde där. Infer körs i vanliga CI/CD-pipelines hos stora teknikföretag och hittar nollpekardereferenser och resursläckor i vanlig Java- och C-kod. Graden av noggrannhet som tillämpas är ett val: fullständig sundhet med formella garantier i ena änden, lätt heuristisk analys i den andra, med de flesta praktiska verktyg någonstans däremellan.

Kan abstrakt tolkning analysera COBOL? Abstrakt tolkning är språkagnostisk som teori. Att tillämpa den på COBOL kräver att man implementerar abstrakta överföringsfunktioner för COBOLs operationer, PIC-fältaritmetik, REDEFINES-klausuler, villkorsnamn på nivå 88 och så vidare. Allmänna verktyg för abstrakt tolkning (Infer, Astrée) stöder inte COBOL. Strukturanalysplattformar för företag som förstår COBOL tillämpar direkt relaterade statiska analystekniker för att hitta kvalitetsproblem, död kod och arkitekturproblem i COBOL-kodbaser.