03.09.2013 Views

Statiskt analysprogram för Rapid - Mälardalens högskola

Statiskt analysprogram för Rapid - Mälardalens högskola

Statiskt analysprogram för Rapid - Mälardalens högskola

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Mälardalens</strong> <strong>högskola</strong> Eskilstuna/Västerås 2008-04-30<br />

<strong>Statiskt</strong> <strong>analysprogram</strong> <strong>för</strong> <strong>Rapid</strong><br />

Student: Harald Lögdahl<br />

Examinator: Professor Björn Lisper<br />

Projektledare: Ingemar Reyier<br />

1


SAMMANFATTNING<br />

Denna rapport redogör <strong>för</strong> ett examensarbete på D-nivå på uppdrag av ABB Robotics i<br />

Västerås. Det går ut på att konstruera ett verktyg <strong>för</strong> att upptäcka problematiska egenskaper<br />

och programkonstruktioner i ABB:s interna programspråk <strong>för</strong> robotar, <strong>Rapid</strong>. Dessa<br />

egenskaper efter kom efterhand att begränsa sig till att bli användning av oinitierade variabler<br />

och kommandon som <strong>för</strong>sätter <strong>Rapid</strong>programmen i vänteläge, när en händelse, som i <strong>Rapid</strong><br />

heter Trap, är den som utlöst detta. Detta verktyg, eller program, måste <strong>för</strong>stå <strong>Rapid</strong> ungefär<br />

på samma sätt som en kompilator, samt dessutom kunna analysera fram olika möjliga<br />

programflöden och vad som händer i dessa.<br />

Verktyget visade sig fullt möjligt att utveckla, metoden <strong>för</strong> detta är densamma som <strong>för</strong><br />

motsvarande statiska analysverktyg <strong>för</strong> andra mer spridda programspråk.<br />

2


INNEHÅLLSFÖRTECKNING<br />

SAMMANFATTNING .............................................................................................................. 2<br />

INNEHÅLLSFÖRTECKNING ................................................................................................ 3<br />

INLEDNING .............................................................................................................................. 5<br />

<strong>Statiskt</strong> <strong>analysprogram</strong> <strong>för</strong> <strong>Rapid</strong>.......................................................................................... 6<br />

1. Motivering......................................................................................................................... 6<br />

1.1 Programanalys av intressanta egenskaper i <strong>Rapid</strong>............................................. 6<br />

1.2 Inte bara en kompilator behövs .............................................................................. 6<br />

1.3 Skal ............................................................................................................................. 6<br />

1.4 Waitstate - Definering ............................................................................................. 6<br />

1.5 Tidsbestämning i programmet............................................................................... 6<br />

1.6 När är waitstate intressant...................................................................................... 7<br />

1.7 Oinitierade variabler - Risker ................................................................................. 7<br />

2. <strong>Rapid</strong>................................................................................................................................. 8<br />

2.1 Användningsområde ................................................................................................ 8<br />

2.2 Moduler och TASK:s ................................................................................................ 8<br />

2.3 Datatyper i <strong>Rapid</strong> ...................................................................................................... 9<br />

2.4 Kommandon i <strong>Rapid</strong>............................................................................................... 10<br />

2.5 Goto .......................................................................................................................... 14<br />

2.6 Trapar ....................................................................................................................... 14<br />

2.7 Filstruktur i <strong>Rapid</strong> .................................................................................................... 15<br />

3. Analys ............................................................................................................................. 16<br />

3.1 Programflöden......................................................................................................... 16<br />

3.2 Programflöden i <strong>Rapid</strong> ........................................................................................... 17<br />

3.3 Waitstate .................................................................................................................. 19<br />

3.4 Programflödesekvationer avseende initiering av variabler .............................. 20<br />

3.5 Oinitierade variabler, parametrar och recordar.................................................. 22<br />

4. Metod .............................................................................................................................. 25<br />

4.1 Programflödesanalys av <strong>Rapid</strong>program och huvudbegrepp ........................... 25<br />

4.2 Lexikal analys .......................................................................................................... 25<br />

4.3 AST och noder – en generell beskrivning........................................................... 25<br />

4.4 AST och noder <strong>för</strong> <strong>Rapid</strong> <strong>analysprogram</strong>met..................................................... 25<br />

4.5 Traversera noder, analysalgoritm ........................................................................ 29<br />

4.6 Traversering av AST i <strong>analysprogram</strong>met .......................................................... 31<br />

5. Lösning - Parser ............................................................................................................ 33<br />

5.1 Inledning................................................................................................................... 33<br />

5.2 Verktyg...................................................................................................................... 33<br />

5.3 AST från Parser ...................................................................................................... 37<br />

5.4 Analysprogrammet - Användning......................................................................... 38<br />

5.5 Analysprogrammet - Programmering .................................................................. 39<br />

5.6 Felmeddelanden och varningar............................................................................ 41<br />

5.7 Orsakskedjor............................................................................................................ 43<br />

5.8 Skal och vidareutveckling <strong>för</strong> andra uppgifter .................................................... 43<br />

6. Relaterat arbete............................................................................................................. 44<br />

6.1 Inledning................................................................................................................... 44<br />

6.2 Coverity..................................................................................................................... 44<br />

6.3 Klocwork................................................................................................................... 45<br />

6.4 Polyspace................................................................................................................. 46<br />

3


SLUTSATSER........................................................................................................................ 48<br />

REFERENSER....................................................................................................................... 49<br />

APPENDIX.............................................................................................................................. 50<br />

4


INLEDNING<br />

Målet med detta projekt är att skapa ett verktyg <strong>för</strong> att kontrollera att inte vissa allvarliga<br />

brister sker i programmeringen av <strong>Rapid</strong>program. <strong>Rapid</strong> är ABB:s interna programspråk <strong>för</strong><br />

robotstyrning. Verktyget ska analysera <strong>Rapid</strong>koden statiskt vilket är i motsats till att den körs<br />

och testas med olika indata. Slutmålet <strong>för</strong> ABB är ett generellt verktyg <strong>för</strong> analys av<br />

<strong>Rapid</strong>progam men i det här examensarbetet valdes två egenskaper ut som testfall, att hitta<br />

användning av oinitierade variabler och att hitta väntekommandon om det är en trap som har<br />

exekverat dessa. Trap är motsvarande avbrott i andra programspråk.<br />

Analysverktyget måste likt en kompilator kunna <strong>för</strong>stå <strong>Rapid</strong>koden samt att ta ut möjliga<br />

programflöden i denna. Till det tar det hjälp av att ett abstrakt syntaxträd, AST byggs upp.<br />

Det består av olika program noder, ett <strong>för</strong> varje kommando, samt att rutiner och moduler har<br />

egna noder. Moduler är i <strong>Rapid</strong> enheter som tillsammans läggs ihop till en körbar enhet. Det<br />

finns systemnära, oftast färdigprogrammerade moduler, och program-moduler som är<br />

specifika <strong>för</strong> själva <strong>Rapid</strong> applikationen, som också kallas task. Analysverktyget ska kunna<br />

analysera programflöden genom alla typer av rutiner och moduler. Analysverktygets uppgift är<br />

ytterst att bidra till bättre programmerade <strong>Rapid</strong>program.<br />

Programmet visade sig fullt möjligt att utveckla. Det körs som en exekverbar Java applikation<br />

där man väljer ut en specifik <strong>Rapid</strong>fil som ligger i ett <strong>Rapid</strong>projekt, eller task. Det går att<br />

konfigurera beträffande vilka väntekommandon som kan vara riskabla samt hur lång tid man<br />

kan vänta på vissa av dessa.<br />

5


<strong>Statiskt</strong> <strong>analysprogram</strong> <strong>för</strong> <strong>Rapid</strong><br />

1. Motivering<br />

1.1 Programanalys av intressanta egenskaper i <strong>Rapid</strong><br />

Meningen med det här examensarbetet är att finna ett sätt att upptäcka intressanta egenskaper i<br />

<strong>Rapid</strong>-programmerade källkodsfiler. <strong>Rapid</strong> är ABB:s interna programspråk <strong>för</strong><br />

robotprogrammering. Det handlar inte om syntaktiska fel utan om fel och brister som kan ge<br />

upphov till problem och oönskade effekter under programkörning. Efterhand blev det bestämt<br />

att de fel som bör upptäckas i <strong>Rapid</strong>programmen var att inte viloläge kan uppstå över en viss<br />

tid om en trap (se avsnitt 2.6) har beställts och exekverats, samt att inte oinitierade variabler<br />

används i t. ex tilldelningssatser på höger sida i uttrycket.<br />

1.2 Inte bara en kompilator behövs<br />

Eftersom det inte direkt är syntaktiska fel som eftersöks så är det inte en ytterligare<br />

<strong>Rapid</strong>kompilator som krävs <strong>för</strong> att lösa uppgiften. En kompilator i vanlig mening behöver<br />

inte, och ABB:s gör det inte heller, undersöka om en variabel som används är initierad<br />

tidigare. Den kollar inte heller om wait-kommandon körs när en trap har beställts eller<br />

undersöker några programflödesvägar (se avsnitt 3.1) utan kontrollerar bara att programmet är<br />

korrekt uppställt rent syntaktiskt. T. ex så måste variabeldeklarationer komma <strong>för</strong>e<br />

rutindeklarationer i en modul eller en subrutin. Men det analysverktyg som kommer att krävas<br />

måste ändå ha många egenskaper som hos motsvarande kompilator <strong>för</strong> att överhuvud taget<br />

kunna <strong>för</strong>stå programspråket som det ska analysera, i det här fallet <strong>Rapid</strong>.<br />

1.3 Skal<br />

Eftersom det inte var helt bestämt vilka egenskaper som <strong>för</strong>st och främst var intressanta i<br />

<strong>Rapid</strong>-programmen så är det givetvis allra bäst om man utan allt<strong>för</strong> stora ansträngningar kan<br />

bygga ut analysen till även andra egenskaper, t. ex död kod. Detta ingår inte i examensarbetet<br />

och inte heller att göra ett kodskelett, eller skal, där man utan att ens behöva programmera<br />

särskilt mycket i de viktigaste filerna kan bygga ut analysen.<br />

1.4 Waitstate - Definering<br />

Definitionen av viloläge är att programmet inte går till något nytt kommando, antingen gör det<br />

ingenting eller så ges det en viss tid att vänta att den aktuella operationen ska bli klar. Vissa<br />

kommandon i <strong>Rapid</strong> kan också sluta innan den angivna tiden om de hinner bli klara innan.<br />

Ytterligare en del väntekommandon handlar om kommunikationen med robotsystemet och<br />

andra handlar om att läsning från en fil ges en viss specificerad tid.<br />

1.5 Tidsbestämning i programmet<br />

Flera waitkommandon väntar bara en viss tid på att få ut<strong>för</strong>a sin åtgärd, frågan är hur lång den<br />

tiden ska få vara i de mest tidskritiska lägena. Det enda rimliga tycker jag är att användaren av<br />

<strong>analysprogram</strong>met själv får specificera det. Hur detta går till finns beskrivet i avsnitt 5.4.2.<br />

6


1.6 När är waitstate intressant<br />

Det är inget problem om programmet hamnar i waitstate, bara inte det är en trap, händelse,<br />

som är orsaken till den. En trap har nämligen triggats igång av en signal (se avsnitt 2.6.3 och<br />

3.2.2) utan<strong>för</strong> robotsystemet och när så är fallet vill man att responsen inte ska ta <strong>för</strong> lång tid.<br />

Det är orsaken och motiveringen till sökningen efter väntekommandon under den<br />

omständigheten. Det aktuella kommandot, t. ex WaitTime, ligger då i trapens programflöde<br />

(se avsnitt 3.2.4). Det kan, men behöver inte vara, inom den subrutin som är själva trapen. Det<br />

kan också vara i en annan rutin som anropats från trapens subrutin, eller i en lång kedja av<br />

subrutiner och funktions och procedur anrop där trapen är den ytterst sändande rutinen.<br />

Analysprogrammet måste alltså kunna avskilja olika programflöden från varandra <strong>för</strong> det kan<br />

också vara så att det är main-funktionen i <strong>Rapid</strong>programmet som är roten i programflödet.<br />

1.7 Oinitierade variabler - Risker<br />

Om en oinitierad variabel används vid t. ex en tilldelning av en annan variabel, så kan man<br />

vara tämligen säker på att i det här fallet <strong>Rapid</strong>-programmeraren haft intentionen att den skulle<br />

vara initierad. Förmodligen är det ett slarvfel som gör att den inte är det, vid sidan om att det<br />

är dålig programmering att den inte är det. Något syntaktiskt fel är det dock inte att använda<br />

oinitierade variabler. Detta fel, där orsaken kanske inte är så enkel att finna, kan också<br />

fortplanta sig mellan rutiner via parametrar. Den mottagande rutinens parameter används som<br />

den vore initierad fast den inte är det, liksom i <strong>för</strong>egående avsnitt så handlar det om att<br />

undersöka programflöden.<br />

7


2. <strong>Rapid</strong><br />

2.1 Användningsområde<br />

<strong>Rapid</strong> är ABB:s interna språk <strong>för</strong> programmering av robotar. Det har många likheter med både<br />

C och Pascal och innehåller kommandon <strong>för</strong> iteration, selektion och subrutiner. Förutom det<br />

så har det många kommandon som är specifika <strong>för</strong> de robotar som ska ta instruktioner från<br />

detta. Dessa gäller bland annat fart, acceleration, möjlig last och positioner <strong>för</strong> roboten att<br />

hålla. Det finns också ett antal direkta Move kommandon, t. ex MoveL där roboten ska röra<br />

sig längs en rak linje. Det är även möjligt att via programspråket ta hand om händelser och<br />

ut<strong>för</strong>a kommandon utifrån dessa, i andra programspråk kallas det vanligtvis <strong>för</strong> ett event, i<br />

<strong>Rapid</strong> <strong>för</strong> trap.<br />

2.2 Moduler och TASK:s<br />

"An RAPID application is called a task. A task is composed of set of modules. A module<br />

contains a set of data and routine declarations".<br />

[RAPID kernel reference] (s. 1)<br />

Den <strong>för</strong>sta sektionen i en modul täcker versioner och språk och kan se ut så här:<br />

%%%<br />

VERSION:1<br />

LANGUAGE:ENGLISH<br />

%%%<br />

[RAPID overview] (s. 88)<br />

Därefter kommer Module-nyckelordet och sedan datadefinitioner som t. ex enligt följande:<br />

MODULE PForders<br />

VAR num newmancmd:=0;<br />

CONST num NOCMD:=0;<br />

CONST num MIN90:=1;<br />

MODULE korresponderar mot ENDMODULE som således ska komma sist i filen. Här liknar<br />

<strong>Rapid</strong> Visual Basic en del.<br />

Sedan kommer rutindeklarationer där återigen de lokala datadeklarationerna ska komma <strong>för</strong>st,<br />

exempel:<br />

PROC changeDock(num dockid)<br />

VAR navPos tmppos;<br />

Efter det kommer kommandona inuti rutinerna. Programmet startar upp i en rutin som heter<br />

main(), alltid utan parametrar, det kan även heta haupt() om det är en tysk module-version.<br />

Endast en main()-rutin får finnas i ett task, fast ett rapid-projekt kan bestå av många filer och<br />

moduler. Ett task är att likna vid ett körbart program på andra plattformar.<br />

8


Det går att nå variabler som är deklarerade som <strong>för</strong>definierade datatyper (se avsnitt 2.3.3) över<br />

alla moduler som ingår i ett task, om dessa är deklarerade i någon. Det går också att anropa<br />

procedurer och funktioner på samma sätt, om dessa inte har nyckelordet LOCAL fram<strong>för</strong><br />

deklarationen, då kan de bara anropas inom modulen. Egendefinierade datatyper (se avsnitt<br />

2.3.4) har dock bara en räckvidd som sträcker sig över den modul som de är deklarerade inuti.<br />

2.3 Datatyper i <strong>Rapid</strong><br />

2.3.1 Olika sorters data<br />

Information kan som i alla programspråk hållas i data, i <strong>Rapid</strong> finns det tre sorters data,<br />

konstanter, variabler och persistants. En konstant får sitt värde i samband med att den<br />

deklareras, en variabels värde kan däremot ändras senare i programmets flöde, en persistant<br />

sparar sitt värde tills nästa programkörning. Data kan också ha olika räckvidd, som i de flesta<br />

programspråk gäller det t. ex variabler inuti en subrutin vs globala. Men i <strong>Rapid</strong> finns det<br />

ytterligare en nivå, nämligen inuti en modul eller <strong>för</strong> hela programmet. Default är i modulen,<br />

men med nyckel ordet GLOBAL kan det gälla <strong>för</strong> hela programmet. Annars kan man <strong>för</strong><br />

tydlighetens skull också använda nyckelordet LOCAL som kan sättas innan deklarationen. Se<br />

exempel:<br />

LOCAL VAR intnum im90;<br />

Om det skulle finnas en global variabel med samma namn så skulle den i exemplet gälla inom<br />

aktuell modul, den gömmer den globala motsvarigheten. Observera att i exemplet så är im90<br />

inte initierad.<br />

När vi sedan närmar oss olika datatyper kan dessa också delas in i kategorierna atomiska,<br />

array och recorddatatyper samt aliasdatatyper. Atomiska innehåller bara ett värde, recordar en<br />

en uppsättning av flera vilka kan vara av olika typ, medan arrayer är en serie av möjliga<br />

element där alla är av samma typ. Det är fullt möjligt <strong>för</strong> en array att innehålla en serie av<br />

arrayer. Alias ikläder sig någon av de <strong>för</strong>stnämnda utan att direkt vara deklarerad så. Man kan<br />

deklarera en variabel som alias-datatypen errnum (som är beskriven i 2.2.2) och den kan då<br />

ges samma värden som datatypen num.<br />

2.3.2 Hur initieras data<br />

Variabler och persistants etc, kan initieras direkt vid sin deklaration som i de flesta<br />

programspråk, Ett exempel:<br />

PERS num GNTraceLevel:=0;<br />

Persistant-variabeln heter GNTraceLevel, har datatypen num och har just nu värdet 0.<br />

Tilldelningtecknet är detsamma på nästan alla andra ställen i <strong>Rapid</strong>koden, endast en viss typ<br />

av parametrar tilldelas på ett annat sätt. Lite senare i rutinen kan man tänka sig programraden:<br />

GNTraceLevel:=1;<br />

och GNTraceLevel blir initierad om den inte redan vore det <strong>för</strong>ut. Initiering kan också ske<br />

som beskrivs i avsnitt 2.4.5, i en rutin som anropas, både som beskrivs där med<br />

parameteröver<strong>för</strong>ing, eller att det helt enkelt handlar om en global variabel med räckvidd över<br />

bägge rutinerna, eller rent utav modulerna. Det allra mest extrema fallen är när initiering sker i<br />

ett annat men synkroniserat task, vid multimove, se avsnitt 2.3.1 om persistants.<br />

Analysverktyget måste kunna identifiera initieringar utan<strong>för</strong> den aktuella subrutinen, och<br />

dessutom om det sker i en annan modul, samt att det med antagandet att den digitala rutinen<br />

som eventuellt anropas initierar den sändande oinitierade parametern, initierar denna.<br />

9


2.3.3 Fördefinierade datatyper<br />

De datatyper som redan är definierade från början i rapid är bool, num och string när det gäller<br />

de atomiska. Det finns tre recorddatatyper som är <strong>för</strong>definierade också, pos, orient och pose,<br />

de uttrycker alla olika sorters koordinater, samt två aliasdatatyper, intnum och errnum.<br />

Intmum används <strong>för</strong> att hålla reda på händelser, errnum <strong>för</strong> fel.<br />

2.3.4 Egen definierade datatyper<br />

Det går även att definiera egna datatyper i <strong>Rapid</strong>. Dessa är vanligen recorddatatyper. Här är<br />

navPos exempel på en sådan från ett riktigt <strong>Rapid</strong>program.<br />

RECORD navPos<br />

num seqwindow;<br />

num x;<br />

num y;<br />

num z;<br />

num angle;<br />

ENDRECORD<br />

2.3.5 Installerade datatyper<br />

De installerade datatayperna har tillkommit bland annat <strong>för</strong> att göra programmen mer lättlästa.<br />

Om man har installerat t. ex de ganska komplexa recordarna robtarget eller speeddata, så kan<br />

man deklarera t. ex variabler utifrån dessa vart som helst. Till skillnad från de egendefinierade<br />

så finns dom inte deklarerade i <strong>Rapid</strong>s källkodsfiler. De tillhör å andra sidan inte heller <strong>Rapid</strong>s<br />

kärna. Det kan finnas många installerade datatyper i den aktuella <strong>Rapid</strong> miljön. Så här<br />

sammanfattas installerade datatyper i relation till de <strong>för</strong>definierade och egendefinierade i<br />

ABB: <strong>Rapid</strong> Kernel Reference dokument:<br />

“Built-in types are a part of the RAPID language while the set of installed or user-defined<br />

types may differ from site to site. From the users point of view there is no difference between<br />

built-in, installed and user-defined types”.<br />

[RAPID kernel reference] (s. 4)<br />

2.4 Kommandon i <strong>Rapid</strong><br />

2.4.1 Selektion<br />

If är i <strong>Rapid</strong> liksom flera uttryck mycket likt sin motsvarighet i Pascal, se t.ex följande<br />

exempel:<br />

If Counter > 100 then<br />

Counter := 100;<br />

ELSEIF Counter < 0 then<br />

Counter := 0;<br />

ELSE<br />

Counter := Counter + 1;<br />

10


ENDIF<br />

[RAPID kernel reference] (s. 46)<br />

Dessutom finns i <strong>Rapid</strong> kommandot CompactIf där man bara kan ha ett villkor samt att bara<br />

en truedel finns tillgänglig. Ett annat Pascal-liknande kommando är Test, se följande<br />

exempel:<br />

TEST choice<br />

CASE 1,2,3:<br />

picknumber := choice;<br />

CASE 4:<br />

stand_by;<br />

DEFAULT:<br />

write consol, "Illegal choice";<br />

ENDTEST<br />

[RAPID kernel reference] (s. 48)<br />

2.4.2 Iteration<br />

Dels finns For som i det här exemplet:<br />

FOR i FROM 10 TO 1 STEP -1 DO<br />

a{i}:=b{i}:<br />

ENDFOR<br />

[RAPID kernel reference] (s. 47)<br />

och dels While som i följande:<br />

WHILE a


ingen parametertyp anges) och REF. Parametertypen INOUT skickar, som namnet antyder,<br />

tillbaka det uppdaterade värdet till den skickande rutinen, i motsats till IN som bara tar emot.<br />

REF är som namnet antyder en referens till den skickande rutinens parameter. De rutiner som<br />

man skapar i <strong>Rapid</strong> kan dock inte vara av typen REF, det gäller bara <strong>för</strong>definierade, eller<br />

digitala rutiner, se avsnitt 2.4.5. Parametertypen INOUT gör att parametern blir VAR eller<br />

PERS beroende på vilket det motsvarande argumentet i den sändande rutinen har.<br />

Vissa parametrar i den mottagande rutinen uppdaterar även den sändande rutinens parametrar,<br />

eller argument. Det är beroende på hur de är deklarerade. IN i den mottagande rutinen<br />

uppdaterar inte den sändande rutinens parameter, eller argument, men INOUT, VAR, PERS<br />

och REF gör det.<br />

Alla parametertyper kan från den sändande rutinen inte nå, eller accessa, alla parametertyper i<br />

den mottagande rutinen, och vissa över<strong>för</strong>ingar mellan parametertyper är inte tillåtna. Se<br />

tabell 2.1.<br />

Tabell 2.1<br />

12


[RAPID kernel reference] (s. 51)<br />

Den sändande rutinens parametrar ligger i höjdled, den mottagande i sidled. Argumenten är<br />

också parametrar i den sändande rutinen. Observera att det kan göra skillnad om den sändande<br />

parametern i sin tur är en parameter, och inte en vanlig rutin variabel. Om den mottagande<br />

parametern är en PERS så ska den sändande också vara det i alla fall. Alla datatyper går att<br />

skicka som parametrar, även arrayer. Här är ett exempel på hur en procedur kan deklareras<br />

med två parametrar varav den ena är en array:<br />

PROC arrmul(VAR num array{*},num factor)<br />

FOR index FROM 1 TO Dim(array, 1) DO<br />

ENDFOR<br />

ENDPROC<br />

[RAPID kernel reference] (s. 63)<br />

array{index}:=array{index}*factor;<br />

Anropas denna procedur med t. ex två VAR deklarerade parametrar kommer den <strong>för</strong>stnämnda,<br />

arrayen, att uppdateras i proceduren. Den mottagande arrayens storlek blir densamma som den<br />

sändande efter * direktivet i denna. Med kommandot Dim fås storleken i form av ett heltal<br />

sedan. När programmet når ENDPROC, ENDFUNC, respektive ENDTRAP, som ska avsluta<br />

rutinkroppen, avlutas rutinen, samt ifall det stöter på kommandot RETURN. Anrop av<br />

funktioner och procedurer ser olika ut beträffande parametrar. Funktioner ska ha ”(” komma-<br />

separerad parameterlista ”)” (undantaget är digitala funktioner, se 2.4.5, med tom<br />

parameterlista). Ett exempel:<br />

CRC:=countCRC(bindata,len+2);<br />

I procedurer så kommer parametrarna, eller argumenten, på rad direkt efter procedurnamnet,<br />

t.ex:<br />

WriteBin maxondev,bindata,1;<br />

2.4.5 Digitala rutiner<br />

Dessa kan lika gärna kallas <strong>för</strong> <strong>för</strong>definierade funktioner i <strong>Rapid</strong>. En sådan som vi sett i<br />

stycket ovan är Dim som ger antalet existerande element i en array. Ett exempel på en digital<br />

procedur som nämnts i avsnitt 2.1 är MoveL som <strong>för</strong>flyttar aktuellt verktyg längs med en linje<br />

till en punkt i ett tredimensionellt rum. T. ex MoveL p1, v500, z10, tool1; De tre <strong>för</strong>sta<br />

parametrarna utgör en koordinat, den fjärde är aktuellt verktyg.<br />

2.4.6 Egendefinierade rutiner<br />

Dessa anropas på precis samma sätt som de digitala. I en modul (se sida 9) ska rutiner<br />

deklareras i den sista sektionen. Ett exempel på en egendefinierad rutin finns i två stycken<br />

ovan. På sida 8 finns ett exempel på en egendefinierad Trap.<br />

13


2.5 Goto<br />

Precis som subrutiner utgör ett hopp i programmet rent syntaktiskt, så gör även GOTO det.<br />

GOTO med ett strängargument gör att programmet hoppar till programraden efter labeln som<br />

är själva argumentet. Ett exempel:<br />

next:<br />

i:=i+1;<br />

------<br />

GOTO next;<br />

[RAPID kernel reference] (s. 39)<br />

Gör att programmet hoppar till raden under next. Det är bara att skriva next, såtillvida det inte<br />

finns någon rutin med åtkomst som heter så redan, och givetvis får det inte finnas någon<br />

digital rutin som heter så <strong>för</strong> då körs den istället. Det är inte tillåtet i <strong>Rapid</strong> att via GOTO<br />

utifrån hoppa in i strukturerade block som loopar och villkorssatser.<br />

2.6 Trapar<br />

2.6.1 Inledning<br />

En trap kopplas kopplas till händelse och om den inträffar så går programmet till den trapens<br />

kod. Efter det fortsätter programmet som vanligt på det ställe som programpekaren var på. En<br />

trap kan dock inte utlösas när en annan trap redan kör. Hur detta <strong>för</strong>hindras och mer går att<br />

läsa i avsnitt 2.6.3.<br />

2.6.2 Deklaration<br />

Att deklarera en trap går till på samma sätt som en procedur eller funktion, fast den kan inte<br />

ha några parametrar eller vara en datatyp. Annars kan den hålla egna datatyper och<br />

kommandon precis som all annan kod. En trap slutar köras då den når kommandot ENDTRAP<br />

(på samma nivå som TRAP) eller när den når kommandot RETURN. Då återgår programmet<br />

till det ställe det var innan trapen exekverades. Ett exempel:<br />

TRAP regulate_trap<br />

ENDTRAP;<br />

VAR num TRAPalpha;<br />

-----------------<br />

-----------------<br />

RETURN;<br />

-----------------<br />

-----------------<br />

2.6.3 Deklaration Associera med händelse<br />

14


Med kommandot CONNECT kopplas en deklarerad trap ihop med en händelse. En händelse<br />

är någon slags input som kommer utan<strong>för</strong> <strong>Rapid</strong>systemet till roboten. Efter CONNECT ska en<br />

identifierare som är av datatypen num eller en alias <strong>för</strong> num följa.<br />

VAR intnum orderint;<br />

CONNECT orderint WITH regulate_trap;<br />

Lika viktigt är det att definiera interruptet och göra det aktivt <strong>för</strong> systemet. Kommandot<br />

ISignalDI gör bådadera. Ett exempel får belysa saken:<br />

ISignalDI sig1, high, orderint;<br />

Kommandot ISignalDI kopplar här samman den <strong>för</strong>definierade händelsen sig1 med orderint<br />

(se exemplet ovan) medan high anger hur stakt utifrån kommande input är.<br />

Med kommandot ISleep kan man deaktivera interruptet, med IDelete kan man ta bort det och<br />

med IDisable kan man göra så att de får stå tillbaka <strong>för</strong> annan kodexekvering. Detta sker t. ex<br />

automatiskt <strong>för</strong> alla andra trapars interupt, när en trap körs.<br />

2.7 Filstruktur i <strong>Rapid</strong><br />

2.7.1 Sysmod<br />

Ett rapidprojekt är rent fysiskt organiserat så att det innehåller två underbibliotek, ett som<br />

heter SYSMOD. Där lägger man som jag <strong>för</strong>står oftast <strong>för</strong>programmerade filer och moduler<br />

som ligger närmast robotsystemet och dess instruktioner, händelser, konstanter mm. Filerna<br />

här har ändelsen .SYS<br />

2.7.2 Progmod<br />

Här ligger den filerna som är specifika <strong>för</strong> själva uppgiften, programmets task kan man<br />

uttrycka det. Här finns också mainrutinen som programmet startar upp ifrån. I övrigt finns det<br />

inga skillnader hur man ställer upp filerna gentemot i SYSMOD-biblioteket. Filerna här har<br />

ändelsen .MOD.<br />

2.7.3 Ihoplänkning av ett <strong>Rapid</strong>projekt<br />

Tasken binds ihop av filerna/modulerna i PROGMOD och SYSMOD-biblioteken. Ett<br />

alternativ är att man har en enda programfil som har tillägget .PRG.<br />

15


3. Analys<br />

3.1 Programflöden<br />

3.1.1 Programflöden generellt<br />

Ett programflöde, eller en programflödesväg kan starta i programmets huvuddel, i exempelvis<br />

C så sker det i main-rutinen. Det kan sedan välja en av true eller false-delen i en if-sats, sedan<br />

gå till ett funktionsanrop och hoppa i programmet rent syntaktiskt till den funktionen, loopa<br />

ett antal varv i en while-sats osv. Antalet programflödesvägar i ett program kan bli väldigt<br />

många, ja till och med oändligt om man inte sätter stopp <strong>för</strong> t. ex allt<strong>för</strong> många rekursiva<br />

anrop i en rutin.<br />

3.1.2 Programflöden i optimerande kompilatorer<br />

Optimerande kompilatorer tar en del hjälp av programflödesanalys <strong>för</strong> att ta bort en del<br />

onödiga rader, t. ex så kan det andra uttrycket i en sekvens av två kanske skrivas på ett enklare<br />

sätt med hjälp av resultatet från det <strong>för</strong>sta, ett exempel:<br />

a := b + c<br />

b := a - d<br />

c := b + c<br />

d := a – d<br />

[Aho, Sethi , Ullman, 88] (s. 600)<br />

Man kan lika gärna skriva d := b i sista raden. Det är uppenbart att alla dessa uttryck ligger i<br />

samma programflödesväg vilket är upp till kompilatorn att lista ut. Optimerande kompilatorer<br />

tar även bort död kod, t. ex så stryks rader där variabler tilldelas om de inte sedan används.<br />

Likaså stryks vägar om villkoret <strong>för</strong> dessa aldrig kan uppnås. För att spara minne så kan<br />

temporära variabler strykas och ersättas med en annan befintlig, om dess värde alltid är<br />

detsamma som dennas. Ytterligare en sak som optimerande kompilatorer kan göra är att byta<br />

ut ordningen på vissa uttryck, om det är smart, <strong>för</strong>utsatt att dessa inte påverkar varandra på<br />

något sätt givetvis. Dessutom sker alltid detta inom samma block.<br />

Optimerande kompilatorer delar in koden i block. Det kan bestå av en eller flera satser. Den<br />

<strong>för</strong>sta programraden inleder alltid ett block, och kod som kan nås via hopp (också hopp via<br />

rutinanrop) inleder också ett nytt block. Att optimera koden inom ett block är inte så svårt,<br />

men fram<strong>för</strong> allt <strong>för</strong> att klara det mellan olika block så använder optimerande kompilatorer<br />

också något som heter Programflödesekvationer. Detta <strong>för</strong> att på ett systematiskt sätt ta reda<br />

på vilken information som finns, och inte är död genom att den skrivits över, in<strong>för</strong> och efter<br />

varje uttryck. Den enklaste Programflödesekvationen ser ut enligt följande:<br />

out[S] = gen[S] U (in[S] - kill[S])<br />

[Aho, Sethi , Ullman, 88] (s. 608)<br />

16


Den information, om t. ex vilka variablers värden som är intressanta, består av det som satsen<br />

S genererat plus informationen innan S minus det som S raderat ut, t. ex genom att ta<br />

minnesutrymmet <strong>för</strong> en variabel i anspråk. Tre andra grundläggande Programflödesekvationer<br />

behandlar: en sats som kan brytas ner till två satser (S -> S1 ; S2), val (selektion) och loopar.<br />

Ett sätt att lösa ekvationssystemet av Programflödesekvationer <strong>för</strong> ett helt program, är att<br />

programmet från början betraktas som en enda sats S, den har inget input överhuvudtaget.<br />

Därefter bryts S ned till delar med Programflödesekvationer och då kan man få statusen, t. ex<br />

beträffande variablers värden, i sitt program vid varje enskild sats.<br />

Lika vanliga är ekvationer som analyserar koden baklänges. Det vill säga man räknar ut vilken<br />

information som finns när man går in i en sats utifrån vilken som <strong>för</strong>elåg när man kom ut ur<br />

det. Återigen är det dock enklast <strong>för</strong> optimerande kompilatorer att jobba med block istället <strong>för</strong><br />

satser. Observera att ett block som kommer efter rent fysiskt i programmet mycket väl kan<br />

generera eller döda information <strong>för</strong> ett block som kommer innan, bara det finns en väg i<br />

programmet dit.<br />

En annan metodik <strong>för</strong> att upptäcka bland annat just oinitierade variabler, generellt, är<br />

”reaching definitions”. Där utgår man från själva definitionen d, av t. ex en variabel i en<br />

tilldelningssats, och tittar på hur länge den sträcker sig genom de nästföljande blocken och<br />

satserna i programmen. Om tidigare nämnda d gäller senare i programpunkten p, där kanske<br />

just denna variabel används, är det är acceptabelt. Definitionen d kan också skrivas över av<br />

andra definitioner innan p, och då gäller istället dessa.<br />

För att veta om en viss information finns om t. ex en variabel finns också begreppen<br />

otvetydiga definitioner av dess värde, och tvetydiga [Aho, Sethi , Ullman, 88] (s. 610).<br />

En direkt tilldelning av en variabel är otvetydig. En tvetydig kan vara en variabel som skickas<br />

in i en funktion som parameter, och det är inte bara värdet som behandlas utav den ("call by<br />

value") utan även referensen, parametern, uppdateras. Det kallas <strong>för</strong> "call by reference", se<br />

avsnitt 1.7 Optimerande kompilatorer måste betrakta även tvetydiga definitioner som gen[S],<br />

men bara otvetydiga kan åstakomma kill[S]. Det som kommer ut ur en sats är givetvis det som<br />

kommer in i nästa. Det går där<strong>för</strong> att beräkna informationen som finns vid varje ställe i varje<br />

programflödesväg. Information som visar sig värdelös på några ställen kan leda till<br />

optimeringar i koden.<br />

3.2 Programflöden i <strong>Rapid</strong><br />

3.2.1 Start av ett programflöde<br />

Ett programflöde, eller en programflödesväg, startar i <strong>Rapid</strong> i satsen main (se avsnitt 2.2)<br />

såtillvida det inte är en händelse som utlöst den via en trap (se avsnitt 2.6). Ett exempel från<br />

ett riktigt <strong>Rapid</strong>program:<br />

PROC main()<br />

nextorder;<br />

ENDPROC<br />

ENDMODULE<br />

Observera att main här är det sista som finns i modulen rent fysiskt. En mainrutin, varken mer<br />

eller mindre, ska finnas i ett program men det behöver inte ligga i någon speciell modul. En<br />

trap kan som sagt starta ett programflöde, bara den är connectad innan, det kan t. ex ske i main<br />

eller i main:s programflöde eller i en annan trap eller traps programflöde. Här ett exempel på<br />

hur en trap startar ett programflöde:<br />

TRAP minus90_trap<br />

IF DIDockOK=0 THEN<br />

17


newmancmd:=MIN90;<br />

ENDIF<br />

ENDTRAP<br />

Man skulle kunna säga att allt som sker i <strong>Rapid</strong>-programmet är en enda programflödesväg i<br />

grunden, där connect och körning av trapar bara är delvägar i denna. Fast det är enklare om<br />

körning av en trap får betraktas som en egen. I analysen av programflödesvägen i<br />

<strong>analysprogram</strong>met (se avsnitt 5) sker så och man kan ställa in om man bara vill titta på<br />

connectade trapars möjliga vägar, eller allas.<br />

3.2.2 En programflödesväg<br />

I exemplet i avsnittet ovan börjar programflödesvägen med main, sedan proceduren nextorder,<br />

program flödesvägen går ovillkorligt dit i det här fallet, i nextorder sker i början följande i<br />

samma <strong>Rapid</strong>program:<br />

MODULE PForderhandl(SYSMODULE)<br />

PROC nextorder()<br />

WaitDO DOtaskPFReady,1;<br />

Den digitala rutinen (se avsnitt 2.4.5) WaitDO körs ovillkorligt, vad som sker i den kan inte vi<br />

eller <strong>analysprogram</strong>met veta exakt. Fast <strong>för</strong>e WaitDO i programflödesvägen ligger faktiskt<br />

parametern DOtaskPFReady som i sin tur är en digital rutin. Det är den digitala funktionens<br />

returvärde som skickas som parameter. Observera att nextorder ligger i en annan modul och<br />

en systemmodul (se avsnitt 2.2). När WaitDO körts fortsätter programflödet till efterföljande<br />

kommandon. Vid selektion (se avsnitt 2.4.1) delar vägen på sig i flera som alla måste<br />

analyseras.<br />

3.2.3 Nästa programflödesväg<br />

Detta berördes något i avsnittet ovan. Ett lite större exempel får belysa hur fler<br />

programflödesvägar kan gestalta sig:<br />

IF relevant(irRightR)>relevant(irRightL) THEN<br />

use_irright:=1;<br />

MPMoveLinDist 0.3,0.02,90;<br />

WaitUntil Abs(relevant(irRightL)- relevant(irRightR))<br />


MPBreak;<br />

ENDIF<br />

Efter truedelen i det yttre villkoret måste även false delen analyseras. Dessa råkar i det här<br />

fallet av en tillfällighet vara ganska lika varandra, men så behöver det inte vara. Även inom<br />

dessa båda programflödesvägar skulle nya vägar kunna utkristallisera sig om villkoret IF<br />

timeout THEN även hade en false del. Observera att true delen inte på något sätt får ändra<br />

villkoren och den tillgängliga informationen beträffande t. ex globala variabler <strong>för</strong> false delen<br />

(se avsnitt 4.5.3).<br />

3.2.4 Trapars programflöde<br />

Hur trapars programflöde startar har berörts i 3.2.1. Observera att om en trap beställs så<br />

bryter den som default pågående programflöde, som startats av main, och går in emellan. En<br />

trap kan dock inte starta om en annan traps programflöde redan pågår.<br />

3.3 Waitstate<br />

3.3.1 Intressanta programflöden beträffande Waitstate<br />

Programmet kan hamna i viloläge under körning av vissa kommandon (se avsnitt 3.3.2). Det<br />

är tillåtet i main:s programflödesväg, men inte i trapars. ABB:s rapidkompilator upptäcker<br />

inte det utan att upptäcka det är en av själva anledningarna till det här examensarbetet. Som<br />

nämnts flera gånger tidigare är det inte bara koden inom själva trapen rent fysiskt som måste<br />

checkas utan i alla trapars möjliga programflödesvägar. Analysen av <strong>Rapid</strong>koden <strong>för</strong> att hitta<br />

väntekommandon sker enligt samma princip som <strong>för</strong> att hitta oinitierade variabler, vilket<br />

beskrivis i avsnitt 3.4. Men det är betydligt enklare att hitta ett väntekommando än att avgöra<br />

om en variabel ska betraktas som initierad eller ej. Det räcker alltid med att väntekommandot<br />

ligger i någon del av programflödesvägen, vid t. ex en if-sats. Dessutom består processen när<br />

det gäller väntekommandon bara av en del, variabler initieras ofta på ett ställe och används på<br />

ett annat.<br />

3.3.2 Kommandon som genererar Waitstate<br />

Definitionen av viloläge är att programmet inte går till något nytt kommando, antingen gör det<br />

ingenting som vid kommandot Waittime, eller så ges det tid att vänta att den aktuella<br />

operationen ska bli klar. Waittime kan sluta innan given tid i tidsparametern också, om en<br />

robot som rör på sig slutar med det. WaitUntil med en boolsk parameter bakom gör att<br />

programmet väntar tills att den är satt till sant. Ett exempel:<br />

PERS bool startsync:=FALSE;<br />

PROC main()<br />

WaitUntil startsync;<br />

--------------------<br />

--------------------<br />

[RAPID overview] (s. 136)<br />

Exemplet är hämtat ifrån Multitasking mellan två robotar. Just persistants (se avsnitt 2.3.1) är<br />

åtkomliga mellan olika processer, tasks i <strong>Rapid</strong>, så startsync uppdateras i det här fallet från en<br />

annan process. De två övriga kommandona som börjar på "wait" är WaitDO och WaitDI.<br />

19


WaitDO är <strong>för</strong> att <strong>för</strong>säkra sig om att en signal från <strong>Rapid</strong> gått ut till Robotsystemet, och<br />

WaitDI <strong>för</strong> att vänta på att en signal in ska komma. Dessutom kan programmet ges tid att<br />

vänta när vissa datatyper ska läsas in från t.ex fil, exempel på sådana kommandon är ReadStr,<br />

ReadNum, ReadAnyBin och ReadBin.<br />

3.4 Programflödesekvationer avseende initiering av variabler<br />

Detta avsnitt behandlar <strong>för</strong>farandet när variabler registreras som initierade. För att de ska bli<br />

det måste de över<strong>för</strong>t till programflödesekvationer bli det via en sats S, och det får inte finnas<br />

någon möjlighet att informationen om att de är det går <strong>för</strong>lorad, t. ex genom selektionssatser<br />

eller loopar. Detta innan variabeln används. Initiering sker ytterst i en tilldelningssats, där<strong>för</strong><br />

är gen (se avsnitt 3.1.2) utelämnat i de andra satsalternativen i tabell 3.1. Fast man kan absolut<br />

tänka sig gen (generate) i en godtycklig sats S. Kill är däremot helt utelämnat, en initiering<br />

kan nämligen inte tas bort. Gen [S] är vänsterledet vid en korrekt tilldelning. Observera att i<br />

tabellen är flera satstyper, <strong>för</strong> loopar och vilkor, uppdelade i två delar. Vanligtvis annars<br />

repressenteras t. ex en If-sats enligt S = if b then S1 else S2. Här är är huvudet i t. ex if-satsen,<br />

vilket är själva vilkoret, skiljt från de i analysen efterkommande satslistorna, dvs de olika<br />

vägarna som programmet kan ta. I praktiken borde de sistnämnda vara de klart viktigaste <strong>för</strong><br />

den här analysen, även om det t. ex är viktigt att loopvariabeln i en while-sats får räknas som<br />

initierad när den eventuellt senare använs. Analysprogrammet protesterar dock givetvis på<br />

samma sätt om oinitierade variabler används i huvudet i en loop eller selektionssats, som om<br />

det sker i någon av dess satslistor.<br />

Tilldelning out [S] = gen [S] U in[S]<br />

Sekvensering av två satser,<br />

Header vid selektion,<br />

Header vid loopar<br />

Satsdel vid selektion där någon<br />

del garanterat körs<br />

Satsdel vid selektion där det inte<br />

är säkert att någon del körs<br />

in [S1] = in [S]<br />

in [S2] = out [S1]<br />

out [S] = out [S2]<br />

in [S1] = in [S]<br />

in [S2] = in [S]<br />

out [S] = out [S1] snitt out [S2]<br />

in [S1] = in [S]<br />

in [S2] = in [S]<br />

out [S] = in [S]<br />

Satsdel <strong>för</strong> loopar in [S1] = in [S]<br />

out [S] = in [S] eller<br />

out [S] = in [S1]<br />

Tabell 3.1<br />

Jag har valt en ansats i det här examensarbetet med att en programflödesväg kan starta i<br />

main() eller i en trap. Hela detta flöde av satser därifrån kan beskrivas som en sats S. För att<br />

bryta ner flödet i mindre satser kan en notation med programflödesekvationer användas. När<br />

en sats bryts ner till två (S -> S1 ; S2), och varken loopar eller selektion är inblandade<br />

beräknas informationen om huruvida variabler är initierade enligt:<br />

in [S1] = in [S]<br />

in [S2] = out [S1]<br />

20


out [S] = out [S2]<br />

S i S -> S1 ; S2 kan i <strong>för</strong>sta steget vara hela programmet, S1 <strong>för</strong>sta satsen innan semikolon<br />

och S2 resten av programmet. I nästa steg är S det som i <strong>för</strong>sta steget är S2 osv.<br />

Vid selektion, t. ex genom en If-sats, ser ekvationerna ut på samma sätt som tabell 3.1 när det<br />

gäller headern (villkorsdelen <strong>för</strong> t. ex If). För satsdelarna handlar det om vägval. S1 är en<br />

väg, t. ex en if-del, S2 en annan, t. ex en else-del. Egentligen kunde ekvationerna ha ställts<br />

upp med ett godyckligt antal vägar. Ekvationerna se ut på två olika sätt, beroende på om<br />

någon satsdel garanterat körs eller inte. Om någon del körs, det gäller If-satser med Else-del<br />

och Test-satser med en Defaultdel, ser ekvationssystemet som i andra alternativet i tabell 3.1:<br />

in [S1] = in [S]<br />

in [S2] = in [S]<br />

out [S] = out [S1] snitt out [S2]<br />

Observera att den nedersta ekvationen inte är densamma som nämns ofta i kompilatorteori<br />

som istället ger:<br />

out [S] = out [S1] union out [S2] [Aho, Sethi , Ullman, 88] (s. 612)<br />

som grundläggande. I den här analysen måste en variabel (som inte är initierad innan<br />

selektionen) initieras i alla vägar. Annars kommer detta att resultera i en varning att den inte<br />

kan garanteras vara initierad vid slutet, out [S], om den senare används. Tabell 3.1 visar hur<br />

detta ser ut vid två vägar, t. ex vid IF där det finns en if och en else-del, men det gäller<br />

generellt vid godtyckligt antal vägar.<br />

Vid selektion där det inte är säkert att <strong>Rapid</strong>programmet tar någon väg genom satsdelarna<br />

gäller programflödesekvationer enligt:<br />

in [S1] = in [S]<br />

in [S2] = in [S]<br />

out [S] = in [S]<br />

Man kan inte vara säker på att initiering sker, även om detta sker <strong>för</strong> t. ex en speciell variabel i<br />

alla befintliga vägar vid selektionen, eftersom möjligheten finns att ingen väg tas. Detta gäller<br />

<strong>för</strong> <strong>Rapid</strong> kommandot CompactIf samt If-satser utan en Else-del och Test-satser utan en<br />

Defaultdel.<br />

Vid loopar, som i <strong>Rapid</strong> kan vara For och While loopar, gäller <strong>för</strong>sta alternativet i tabell 3.1<br />

<strong>för</strong> headern, informationen som denna genererar <strong>för</strong>s alltid vidare. Antalet gånger som<br />

loopens header utvärderas är en. Vilka slutvärden som variabler tenderar att få i loopens<br />

header är inte intressanta, bara om de är/blir initierade. Där<strong>för</strong> antas resultatet bli detsamma<br />

<strong>för</strong> koden i headern oavsett hur många gånger som den verkligen utvärderas<br />

För satsdelen <strong>för</strong> loopar gäller sista alternativet i tabell 3.1. Den grammatiska är<br />

beskrivningen S -> S1, där S är en generell sats, medan S1 säger mer specifikt att det är en<br />

loops satsdel.<br />

in [S1] = in [S]<br />

out [S] = in [S] eller out [S] = in [S1]<br />

21


Loopen har en entry punkt till sig själv, men det tar inte den här analysen hänsyn till, där<strong>för</strong><br />

den <strong>för</strong>sta ekvationen. I den andra ekvationen kan man välja om man vill <strong>för</strong>hålla sig till S,<br />

som säger att det är en sats, och S1 som säger att det är en loop. Det som S1 (och S) genererar<br />

i form av eventuella initieringar tar den här analysen inte hänsyn till efteråt, eftersom det inte<br />

är helt säkert att detta kommer att ske (0 varv). En loops satsdel påverkar här alltså inte alls<br />

informationen <strong>för</strong> vad som kommer ut ur den. Detta <strong>för</strong> att informationen om huruvida<br />

variabler med mera blivit initierade ska vara säker.<br />

Observera dock att när S1 bryts ned inuti loopen, i egna satser, så kan det ske enligt de andra<br />

ekvationssystemen, om initieringar sker då så är dom intressanta just inuti loopens satsdel.<br />

Programflödet kan göra ”hopp” i <strong>Rapid</strong>koden med funktions och proceduranrop (se t. ex<br />

4.5.4), samt via GOTO. Självklart ska informationen innan hoppet tas med till<br />

hoppdestinationen samt tillbaka när hoppet är gjort. I övrigt så påverkas och <strong>för</strong>svåras inte<br />

analysen just av att man flyttar programpekaren. Det är inte tillåtet i <strong>Rapid</strong> att via GOTO<br />

hoppa in i loopar och satslistor, se avsnitt 2.5. Då hade analysen också blivit mycket svårare.<br />

3.5 Oinitierade variabler, parametrar och recordar<br />

3.5.1 Hur upptäcka att variabler ej blivit initierade<br />

Hur variabler initieras finns beskrivet i avsnitt 2.3.2. När en variabel sedan används, t. ex på<br />

höger sida i en tilldelningssats eller vid utskrift eller när den skickas som en parameter till en<br />

annan rutin, måste man veta att den verkligen är initierad, det är den andra anledningen till<br />

just det här examensarbetet. Jag har valt att spara information om variablers tillstånd i något<br />

som jag kallar <strong>för</strong> symboltabeller, där initiering med mera, finns sparat. Har variabeln inte<br />

hunnit tilldelas under programflödesvägens gång när den används, så kommer detta att<br />

upptäckas tack vare symboltabellernas information.<br />

3.5.2 Symboltabeller<br />

Symboltabeller används i alla kompilatorer och <strong>för</strong>modligen alla analysverktyg som detta. De<br />

innefattar också här, såväl rutiner som variabler etc. I kompilatorer är en av<br />

huvudanledningarna typkontroll, här är det att hitta oinitierade variabler. I många kompilatorer<br />

skapar man en ny symboltabell <strong>för</strong> varje rutin, samt en global. Här skapas inte nya<br />

symboltabeller på det kriteriet, utan istället kan den dela sig i flera om det dyker upp separata<br />

programflödesvägar. Läs mer om det som jag kallar symboltabeller, och dess medlemmar och<br />

plats i programmet i avsnitt 5.5.4.<br />

3.5.3 Globala variabler<br />

Med globala variabler menas i programmering generellt variabler som har en räckvidd som<br />

sträcker sig över hela programmet. De deklareras inte i någon rutin, inte heller i main, utan<br />

utan<strong>för</strong>. Under ett programflödes gång så finns det alltid bara en instans av variabeln att hålla<br />

reda på, även om man kan behöva så att säga återställa informationen om denna när en ny<br />

programflödesväg ska analyseras (se avsnitt 5.5.8). I <strong>Rapid</strong> så kan även variabler gälla inom<br />

modulen och inom hela programmet.<br />

3.5.4 Lokala variabler<br />

Lokala variabler har bara en räckvidd inom den egna subrutinen. Om programflödet sträcker<br />

sig utan<strong>för</strong> den, men man fortfarande är intresserad av att hålla reda på informationen om<br />

22


variabeln, så får man skicka den via en parameter. Kompilatorer och stackkodsprogram som<br />

använder symboltabeller skapar i regel en ny symboltabell (se avsnitt 5.5.5) dynamiskt, med<br />

de lokala variablerna. Observera att det vid t. ex rekursiva anrop av en rutin mycket väl kan<br />

finnas flera instanser av en lokal variabel på stacken samtidigt. Dessa har då ingenting med<br />

varandra att göra mer än att de heter likadant och finns i en rutin som också gör det. Om det i<br />

<strong>Rapid</strong> finns en variabel som anropas inom subrutinen, och det finns en lokal, lokal <strong>för</strong><br />

modulen och global <strong>för</strong> hela programmet, som alla heter likadant, så är det den lokala<br />

subrutinens som nås i <strong>för</strong>sta hand.<br />

3.5.5 Parameteröver<strong>för</strong>ing<br />

Variabler med alla typer av räckvidd kan skickas in i en rutin, procedur eller funktion, som<br />

parameter. Beroende på hur den är deklarerad så kan den antingen bara initiera värdet på den<br />

mottagande rutinens variabel så kallat "Call by value" eller så kan den uppdatera även värdet<br />

på den sändande rutinens variabel "Call by reference". Man kan till och med skicka in<br />

oinitierade variabler i en subrutin enbart <strong>för</strong> att de ska bli tilldelade och initierade.<br />

Analysprogrammet måste under alla omständigheter klara av att hantera detta sätt <strong>för</strong> variabler<br />

och parametrar att bli tilldelade och initierade. Dessutom måste det finnas en metod <strong>för</strong> att<br />

hantera variabler som skickas in som parametrar till digitala rutiner, där källkoden inte synlig.<br />

3.5.6 Recorddatatyper<br />

3.5.6.1 Vilka medlemmar finns i recorden<br />

Här är ett exempel på en recorddeklaration från ett riktigt <strong>Rapid</strong>program:<br />

RECORD navPos<br />

num seqwindow;<br />

num x;<br />

num y;<br />

num z;<br />

num angle;<br />

ENDRECORD<br />

Inga konstigheter, det finns t. ex recordar som i sin tur innehåller recordar, installerade<br />

datatyper mm. Så här deklareras en instans av navPos på ett ställe som lokal variabel:<br />

VAR navPos tmppos;<br />

Och så här initieras tmppos och används medlemmar, som argument i en procedur, i samma<br />

rutin:<br />

tmppos:=globNavPos;<br />

MPSetPos tmppos.x,tmppos.y,tmppos.angle;<br />

En symboltabell måste, oavsett om den är lokal eller global, hålla reda på vad som händer med<br />

varje recordmedlem, precis som om den vore en atomisk variabel (se avsnitt 5.5.7). Även<br />

enklare kompilatorer måste hålla reda på vilka medlemmar som finns så att punktoperatorn<br />

alltid används på rätt sätt.<br />

3.5.6.2 Recordmedlemmars initiering<br />

En initiering av en recordmedlem skulle kunna se ut enligt följande:<br />

tmppos.z = z;<br />

eller:<br />

tmppos.z = globNavPos.z;<br />

Det måste märkas i symboltabellen att tmppos.z har blivit initierad efter detta, <strong>för</strong>utsatt att z<br />

och globNavPos.z är initerad givetvis. Annars måste <strong>analysprogram</strong>met varna <strong>för</strong> att<br />

oinitierade variabler används.<br />

23


3.5.6.3 En hel records initiering<br />

Ett exempel på det har vi redan sett, nämligen:<br />

tmppos:=globNavPos;<br />

Nu bör symboltabellen indikera på att tmppos är helt initerad, <strong>för</strong>utsatt att globNavPos var det<br />

givetvis. Ett annat sätt <strong>för</strong> tmppos att bli det är att alla dess medlemmar blir initierade var <strong>för</strong><br />

sig. Här är också ett exempel på deklaration samt initiering av en välbekant record från samma<br />

<strong>Rapid</strong>program som tidigare:<br />

PERS navPos globNavPos:=[0,0,0,0,0];<br />

3.5.6.4 Parameteröver<strong>för</strong>ing av recordar<br />

Det är ingen skillnad på att skicka recordar eller arrayer av t. ex heltal (se avsnitt 2.4.4) som<br />

parametrar gentemot atomiska variabler. Samma regler gäller <strong>för</strong> huruvida "Call by value"<br />

eller "Call by reference" gäller. Om en funktion är deklarerad <strong>för</strong> att ha t. ex navPos i sin<br />

parameterlista, så är det just en recordinstans av navPos som ska skickas och tas emot i den<br />

sändande respektive mottagande funktionen. Analysprogrammet och dess symboltabeller<br />

måste kunna hantera kombinationen av parameteröver<strong>för</strong>ing samt recordmedlemmars och hela<br />

recordars initieringsprocess.<br />

24


4. Metod<br />

4.1 Programflödesanalys av <strong>Rapid</strong>program och huvudbegrepp<br />

För att kunna analysera programflöden i <strong>Rapid</strong>, och lösa uppgifterna med att hitta kommandon<br />

som eventuellt styr in <strong>Rapid</strong>programmen i vänteläge i en trap:s programflöde, samt <strong>för</strong>hindra<br />

att oinitierade variabler används, krävs ett antal steg och hjälpmedel på vägen. Dessa är<br />

generella <strong>för</strong> all kompilatorteori och programanalys, även om de här är tvungna att anpassas<br />

efter just det här programspråket, <strong>Rapid</strong>, och hur det är uppbyggt. En kompilator ut<strong>för</strong> <strong>för</strong>st en<br />

lexikal analys <strong>för</strong> den källkod som den får givet och som den ska transformera till ett<br />

<strong>för</strong>modligen mer maskinnära språk. Där sker kontroll att programmet är uppbyggt av rätt<br />

beståndsdelar, det kan vara siffror, identifierare och nyckelord, ofta kallas dessa gemensamt<br />

<strong>för</strong> Tokens. Därefter kommer den syntaktiska analysen, alla Tokens kan nämligen inte<br />

kombineras hur som helst, utan enligt givna mönster. Efter det kommer den semantiska<br />

analysen, där analyseras vad programmet egentligen gör och allt är inte tillåtet här heller. Alla<br />

datatyper kan t. ex inte tilldelas varandras värden. Det kallas typkontroll och ligger alltså i den<br />

semantiska analysen.<br />

4.2 Lexikal analys<br />

Den lexikala analysen <strong>för</strong> <strong>analysprogram</strong>met skiljer sig inte från vad som <strong>för</strong>väntas av en<br />

kompilator. Självklart måste man skriva kommandona och de olika identifierarna på ett<br />

syntaktiskt korrekt sätt. <strong>Rapid</strong> består liksom alla programspråk av ett antal Tokens, eller idem.<br />

Dessa är bland annat rena nyckelord som t. ex while, num, true osv, men kan också vara ident<br />

(identifierare) De olika idem:en passar ihop enligt ett visst mönster, ett exempel är<br />

kommentarsatser i <strong>Rapid</strong> vilket är ett utropstecken, text och radbrytningstecken. Det säger sig<br />

självt att t. ex en reglerna <strong>för</strong> t. ex en for-loop är mer komplexa, men principen är densamma.<br />

4.3 AST och noder – en generell beskrivning<br />

Under en lexikal analys så bygger kompilatorer vanligtvis samtidigt upp ett abstrakt<br />

syntaxträd, AST. Överst i det ligger vanligtvis själva programmet, eller Programnoden. Under<br />

programnoden kommer i ett procedurorienterat programspråk, utan klasser mm, noder <strong>för</strong><br />

rutiner, t. ex Procedurer och Funktioner. Dessa har i sin tur undernoder <strong>för</strong> varje kommando<br />

och sedan kanske även <strong>för</strong> delar av dessa, t. ex uttryck. Variabler mm sparas också i det<br />

abstrakta syntaxträdet både i den översta Programnoden, om de är globala, och i rutinernas<br />

noder, om de är lokala. Det är nödvändigt med ett AST om man ska gå vidare med en<br />

semantisk analys av ett program, oavsett programspråk.<br />

4.4 AST och noder <strong>för</strong> <strong>Rapid</strong> <strong>analysprogram</strong>met<br />

Noderna i <strong>analysprogram</strong>mets AST är ganska lika en vanlig kompilator, men något enklare.<br />

Uttryck (exempelvis 1+1) är inte en egen nod här, utan allt innehåll i t. ex en assign sats (t. ex<br />

a := 1 + 1) bearbetas direkt i Assignnoden, såtillvida att det inte finns undernoder i uttrycket.<br />

Det kan t. ex vara a := 1 + calculatesomething(1) , alltså ett funktionsanrop.<br />

25


Tabell 4.1<br />

TopNode<br />

Moduler<br />

Moduler (vektor)<br />

Attribut (vektor)<br />

Typer (vektor)<br />

Variabler (vektor)<br />

Variabelnamn<br />

RoutineDefNode (vektor)<br />

RoutineDefNode<br />

Namn<br />

Lokal (boolsk variabel, om synlig utan<strong>för</strong> modulen)<br />

Variabler (vektor)<br />

Variabelnamn<br />

Satser (vektor)<br />

Noder<br />

Error - Satsdel (vektor)<br />

Noder<br />

Parameterlista (vektor)<br />

Parametrar<br />

Assign<br />

CompactIf<br />

If<br />

Vänsterled (targetImage)<br />

Högerled (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Villkor (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Sats (Vektor)<br />

Nod<br />

Villkor (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Satsdel (vektor)<br />

Noder<br />

ElseIfNoder (Vektor)<br />

ElseIfNod<br />

Else - Satsdel (vektor)<br />

Noder<br />

26


ElseIfNod<br />

Test<br />

CaseNod<br />

For<br />

While<br />

Villkor (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Satsdel (vektor)<br />

Noder<br />

Test (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Casedelar (vektor)<br />

CaseNod<br />

Default(vektor)<br />

Noder<br />

Villkor (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Satsdel (vektor)<br />

Noder<br />

Loopvariabel<br />

Variabel<br />

Parameter<br />

Fromvillkor (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Tovillkor (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Stegdel (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Satsdel (vektor)<br />

Noder<br />

Villkor (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

27


Goto<br />

Label<br />

Connect<br />

Return<br />

Parametrar<br />

Satsdel (vektor)<br />

Noder<br />

GotoLabel<br />

Namn<br />

TrapNamn<br />

Label<br />

Expression (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

ProcedureCall<br />

Parameterlist (vektor)<br />

Literaler<br />

Variabler<br />

Parametrar (från en annan sändande rutin)<br />

Procedurnamn<br />

FuncCall<br />

Raise<br />

Retry<br />

TryNext<br />

Exit<br />

Parameterlist (vektor)<br />

Literaler<br />

Variabler<br />

Parametrar (från en annan sändande rutin)<br />

Funktionsnamn<br />

Expression (vektor)<br />

Noder<br />

Literaler<br />

Variabler<br />

Parametrar<br />

Radnr<br />

Radnr<br />

Detta är vilka noder som strikt sett finns i analysverktygets AST, med avseende på just<br />

analysdelen, samt de mest intressanta attributen till dessa noder. Observera att i t. ex Assignnodens<br />

högerled kan det ligga noder, men inte alla typer av noder,<br />

analysdelen av analysverktyget kontrollerar dock inte vilken nodtyp som ligger där. En<br />

kontroll att detta är riktigt har nämligen redan skett i den lexikala analysen. En intressant<br />

egenskap finns i t. ex högerledet i en assignsats. Det kan betraktas som ett uttryck, Expression.<br />

28


Det är i strikt bemärkelse inte en nod här. Jag anser inte att det måste vara så då<br />

<strong>analysprogram</strong>met i det här avseendet har lägre krav än en kompilator och dess semantiska<br />

analys, se avsnitt 4.1. Det finns t. ex inget krav på att hålla reda på prioritet mellan operander.<br />

Operander finns <strong>för</strong> övrigt inte alls med här. När man analyserar "Noder", "Literaler",<br />

"Variabler" och "Parametrar" i avsedd vektor i Assignnoden, så sker det på ett systematiskt<br />

sätt där <strong>analysprogram</strong>met kan behöva bearbeta exempelvis funktionsanrop och dess typ av<br />

nod. Allting i högerledet måste vara initierat <strong>för</strong> att vänsterledet ska kunna bli det också. Det<br />

hade kunnat gå att utelämna konstanter som t. ex siffran 1 i exemplen i början av avsnittet 4.4.<br />

4.5 Traversera noder, analysalgoritm<br />

4.5.1 Inledning<br />

Analysprogrammet loopar sig fram genom sitt AST som beskrivs generellt i pseudokoden i<br />

4.6. Topnoden håller moduler som i sin tur håller rutiner. Noderna <strong>för</strong> dessa heter i tur och<br />

ordning TopNode, Module och RoutineDefNode. Har den kommit fram till main rutinen,<br />

eller, en trap, så påbörjas analysen av en programflödesväg, se även avsnitt 3.2.1.<br />

RoutineDefNode håller alla satser och kommandon som inte ligger innan<strong>för</strong> block, i t. ex en If<br />

sats.<br />

Vilka vägar programflödet tar vid kommando noder beskrivs i kapitlen nedan.<br />

I samband med den lexikala analysen har inte bara ett AST byggts upp, utan även en standard<br />

symboltabell, se avsnitt 5.5.4. I den finns bland annat alla rutiner representerade, vad de heter,<br />

typ (funktion, procedur, trap), räckvidd och radnummer mm. Denna information är behjälplig<br />

när programflödet gör hopp genom t. ex funktionsanrop. I symboltabellen finns också alla<br />

typer av variabler registrerade, lokala som globala. Har de inte tilldelats direkt vid<br />

deklarationen så registreras de <strong>för</strong>st som oinitierade.<br />

4.5.2 Iteration<br />

När en programflödesväg i <strong>Rapid</strong> går in i en Whilenod så utvärderderas <strong>för</strong>st villkorsdelen.<br />

Dess olika delar ligger i en lista (vektor), t. ex så motsvaras uttrycket a < b + 1 av listan a,b,1.<br />

a och b måste i det här läget vara initierade, konstanten 1 behöver naturligtvis inte analyseras<br />

och hade egentligen inte behövt finnas i listan. Det kan även ligga noder i listan, t. ex en<br />

funktionsanropsnod. Analysprogrammet analyserar i så fall den, se avsnitt 4.5.4, särskilt det<br />

som gäller parameteröver<strong>för</strong>ing, tillståndet <strong>för</strong> dessa i den sändande rutinen skickas med i en<br />

egen struktur i <strong>analysprogram</strong>met. De mottagande parametrarna finns däremot i<br />

funktionsanropsnoden.<br />

Villkorsdelen <strong>för</strong> Whilenoden påverkar alltid symboltabellen <strong>för</strong> det fortsatta programflödet.<br />

När programflödet når Fornoden registreras <strong>för</strong>st den eventuella loopvariabeln som initierad i<br />

symboltabellen. Därefter sker samma sak i tur ordning <strong>för</strong> delarna From och To. Step kan<br />

däremot inte antas med säkerhet köras något varv. I regel så räknar Step bara upp eller ned<br />

loopvariabeln, som alltså är initierad i det här läget.<br />

Symboltabellen påverkas alltid av de nämnda delarna av Fornodens huvud <strong>för</strong> fortsättningen<br />

av programflödet.<br />

Därefter utvärderas alltså satsdelen, det gäller både <strong>för</strong> While och For. Där ligger<br />

kommandonoderna i en lista <strong>för</strong> While respektive och For. I programanalysens synpunkt så<br />

ligger dom i ett block, vilket inte är svårare att analysera än satser i en rutin. Efter att listans<br />

alla element gåtts igenom så återställs alltid symboltabellerna till läget som var efter<br />

29


villkorsdelen (huvudet), och innan satsdelen. De eventuella initieringar av variabler som skett<br />

i satsdelen är nämligen inte säkra, se gärna avsnitt 3.4 som resonerar kring<br />

programflödesekvationer <strong>för</strong> denna analys. Att de beaktas överhuvudtaget beror på att de är<br />

intressanta just inom loopkroppens kod.<br />

4.5.3 Selektion<br />

Först utvärderas villkorsdelen i If, CompactIf och Test-noderna vilket sker på samma sätt som<br />

i motsvarande <strong>för</strong> While. Observera dock listorna av undernoderna ElseIf respektive Case.<br />

Inverkan på symboltabellerna blir också lika som vid While:s villkorsdel, d v s total. Till<br />

skillnad mot <strong>för</strong> loopar så finns dock här flera olika vägar, eller olika satsdelar som var och en<br />

måste analyseras. Analysen av dessa vägar startar med en symboltabell som ser likadan ut <strong>för</strong><br />

alla. När alla vägar är analyserade jämkas symboltabellerna samman och initieringar som skett<br />

i samtliga kommer att gälla <strong>för</strong> den symboltabellen som ska gälla efter t. ex en If-nod. Detta<br />

gäller när någon programflödesväg måste tas och finns beskrivet i avsnitt 3.5 och tabell 3.2.<br />

Om ingen satsdel någonstans måste köras, återställs helt enkelt symboltabellen till det som<br />

den var strax efter villkorsdelen. Se tabell 3.3.<br />

4.5.4 Funktionsanrop<br />

Analysprogrammet anropar en särskild rutin som letar upp funktionen i symboltabellen och<br />

programmet. I symboltabellen finns även information om på vilket radnummer den är<br />

deklarerad. I den underrutinen i <strong>analysprogram</strong>met stegas <strong>Rapid</strong> funktionens noder och satser<br />

igenom.<br />

Analysprogrammet skickar en struktur med alla <strong>Rapid</strong>parametrar och tillståndet <strong>för</strong> dom till<br />

rutinen stepthroughfunction, strukturen skickas sedan vidare till den anropade rutinen och<br />

talar där om huruvida parametrarna är initierade eller ej. Den anropade rutinen genomgår<br />

alltså en kontextkänslig analys. Även recorddatatyper skickas på exakt samma sätt.<br />

En <strong>för</strong>utsättning <strong>för</strong> om den sändande funktionens parametrar ska kunna uppdateras, eller som<br />

är intressant i det här examensarbetet, initieras, är givetvis att rätt typer av variabler är<br />

inblandade. Har den mottagande funktionens variabler deklarerats som IN ska detta inte ske<br />

(se avsnitt 2.4.4). Funktionen stepthroughfunction får tillbaka en uppdaterad symboltabell <strong>för</strong><br />

varje sats som den kör, när alla noder (satser) är bearbetade jäm<strong>för</strong>s de sändande och<br />

mottagande parametrarna. Om parametertyperna är de rätta så kommer de sändande<br />

parametrarnas bakomliggande variabler att initieras, om de inte redan är det. Detta sker med<br />

hjälp av den i avsnitten tidigare nämnda parameterstrukturen.<br />

Om den anropade funktionen inte finns i symboltabellen så är det en digital funktion (se<br />

avsnitt 2.4.5). Man kan därefter ställa in om man vill anta att eventuella parametrar ska<br />

uppdateras (d v s, de tilldelas i den digitala funktionen) eller om man vill att de inte ska göra<br />

det. En grund<strong>för</strong>utsättning <strong>för</strong> detta är parametrarna som man skickat är av typen INOUT.<br />

Väntekommandon är digitala funktionsanrop eller proceduranrop, se nästa avsnitt. Om det<br />

också visar sig att dessa inte är acceptebla samt att det är en trap som utlöst<br />

programflödesvägen så ska <strong>analysprogram</strong>met rapportera det som ett fel. Observera att här<br />

måste det inte vara säkert att väntekommandot körs vid exempelvis selektion. Det räcker att<br />

möjligheten finns <strong>för</strong> att <strong>analysprogram</strong>met ska rapportera det som ett fel.<br />

4.5.5 Proceduranrop<br />

Förfarandet med proceduranrop skiljer sig inte mycket från funktionsanrop. Men dessa kan<br />

givetvis inte fungera i högerledet i en tilldelningssatts. Att syntaxen skiljer sig något i vid<br />

30


anrop av procedurer spelar i det här läget egentligen ingen roll, eftersom den lexikala analysen<br />

redan är avklarad och det abstrakta syntaxträdet redan skapat.<br />

4.5.6 Rekursivitet<br />

Exempelvis rekursiva funktioner (som är tillåtna i <strong>Rapid</strong>) skulle kunna bli ett problem <strong>för</strong><br />

<strong>analysprogram</strong>met. Samma funktion skulle kunna analysera sig själv gång på gång, alltså<br />

evighetsloopa. Samma sak skulle kunna ske om en eller flera rutiner ligger mellan anropen till<br />

sig själv i programflödet. Där<strong>för</strong> är en spärr satt att en rutin inte får analysera sig själv i sitt<br />

fortsatta programflöde, utan analysen bara fortsätter med nästa kommando. Häri ligger i<br />

princip en begränsning i den kontextkänsliga analysen, se avsnitt 4.5.4, vissa möjliga<br />

parametervärden till vissa funktionsanrop kanske aldrig blir utvärderade. Däremot kommer<br />

fortfarande all <strong>Rapid</strong>källkod att bearbetas under analysen minst en gång, fast alltså möjligtvis<br />

inte i alla kontexter.<br />

4.5.7 Goto<br />

Vid <strong>Rapid</strong>kommandot Goto så dirigerar <strong>analysprogram</strong>met programflödesvägen till den<br />

efterföljande identifieraren, här labeln, genom att söka upp vart den finns i <strong>Rapid</strong>programmet.<br />

Även labels varhelst de är deklarerade, finns i symboltabellerna, även om dessa främst är till<br />

<strong>för</strong> variabler som kan initieras etc. Lablarna kunde även ha haft en egen struktur, som<br />

dessutom inte skulle behöva uppdateras efterhand.<br />

Programmet kollar alla satser efter där labeln är deklarerad tills den aktuella rutinen tar slut<br />

eller gör Return. Därefter fortsätter programflödesanalysen till satser efter Gotoanropet.<br />

Observera att symboltabellen från Gotoanropet skickas med som parameter till<br />

<strong>analysprogram</strong>mets underrutin gotoLabel, samt att den i en uppdaterad variant returneras<br />

tillbaka. Det är inte tillåtet i <strong>Rapid</strong> att hoppa in loopar, eller satslistor innan<strong>för</strong> block över<br />

huvud taget, med hjälp av Goto. Det hade komplicerat traverseringen av noder i alla beskrivna<br />

fall, inklusive detta.<br />

4.6 Traversering av AST i <strong>analysprogram</strong>met<br />

If, For, While, Funktionsanrop, Assign mm har varsin en egen nod. Assign består av ett<br />

vänsterled som tilldelas, token :=, och ett högerled med den <strong>för</strong>hoppningsvis givna<br />

informationen. Till skillnad mot de övriga noderna, med undantag av funktions och procedur<br />

anrop till digitala rutiner i vissa fall, kan även assign initiera variabler. Notera dock att det<br />

<strong>för</strong>st är viktigt att kolla att allt i högerledet (som kan innehålla undernoder, identifierare och<br />

rena literaler) är helt intakt beträffande initiering. I Appendix finns hela <strong>analysprogram</strong>mets<br />

arbetssätt beskrivet som pseudokod. Denna ska inte <strong>för</strong>växlas med den riktiga Javakod som<br />

<strong>analysprogram</strong>met består av, och är inte är ett tvärsnitt av den riktiga koden, mycket är<br />

bortskalat, men principen är motsvarande <strong>för</strong> hur det abstrakta syntaxträdet traverseras <strong>för</strong> att<br />

hitta intressanta egenskaper i <strong>Rapid</strong>programmens källkod. Observera att mer komplexa noder<br />

som t. ex For också har undernoder, dessa är From, To och Step samt räknaren som i och <strong>för</strong><br />

sig inte är en nod, i huvudet. Dessutom består loopkroppen av en lista med kommandon, som<br />

är noder. Synen på initieringar som sker inom en komplex nod är olika efteråt beroende på typ<br />

av nod samt om programflödet garanterat går igenom någon del eller inte gör det. Detta<br />

<strong>för</strong>klaras schematiskt i avsnitt 3.4.<br />

31


Alla moduler stegas igenom, på jakt efter mainrutinen och trapar i loopen innan<strong>för</strong> den.<br />

Därefter kollas kommandona, vilka är noder. Dessa kan vara direkt underställda rutinerna<br />

under traversering, eller finnas inom ett block, t. ex i satsdelen i en If-sats. I pseudokoden i<br />

Appendix hänvisas ofta enligt //Run a routine with code “if command is assign then {“<br />

etc. vilket i själva verket avser ett rekursivt anrop i <strong>analysprogram</strong>met. Rutinen som sköter<br />

allt detta i verkligheten, rekursivt anropad eller ej, behöver dock inte alla yttre loopar eftersom<br />

man redan har greppat rätt nod.<br />

I Assign kontrolleras <strong>för</strong>st allt i högerledet, vilket kan bestå av literaler (siffror, strängar,<br />

boolska variabler), identifierare och funktionsnoder. Högerledet betraktas i själva verket som<br />

ett uttryck (i vilket allt måste vara initierat). Hur uttryck undersöks finns beskrivet i början av<br />

kapitlet 4.5.2. Identifierarna, och huruvida dom är initierade, finns lagrat i den symboltabell<br />

som används då. Den nedre delen efter den innersta loopen beskriver hur identifierare i<br />

vänsterledet kan bli initierade i symboltabellen i fallet Assign. Kontroll av initering och<br />

initiering i sig sker under samma (och enda) fas av traverseringen av AST. Däremot ligger<br />

kontrollen av att programmet inte går in i vänteläge (om en trap har startat<br />

programflödesvägen) i ett eget pass i den verkliga källkoden. Det hade fungerat att ha den i<br />

samma fast det hade blivit lite rörigare. Ett väntekommando är alltid en digital rutin (se avsnitt<br />

2.4.5) och hittas under traversering i funktionsanrop och proceduranrop.<br />

32


5. Lösning - Parser<br />

5.1 Inledning<br />

För att kunna analysera <strong>Rapid</strong>källkodsfiler krävs ett verktyg/program som <strong>för</strong>står språket<br />

<strong>Rapid</strong>, och som kan bygga upp ett abstrakt syntaxträd, AST (se avsnitt 4.3) samt analysera<br />

programflödet i detta. Detta <strong>analysprogram</strong> är fullt möjligt att programmera från grunden, fast<br />

ett enklare och mer strukturerat sätt är att använda någon av de så kallade kompilatorkompilator<br />

verktyg som redan finns. Dessa tar in en fil med regler <strong>för</strong> språket, i det här fallet<br />

<strong>Rapid</strong>, och översätter det till en kompilator skrivet i ett vedertaget programspråk som i de<br />

följande nämnda fallen C++ och Java. Sker en ändring i regelfilen ändras C++ eller<br />

Javakoden. Det är också möjligt, och menat, att man ska kunna ändra den utkommande C++<br />

eller Javakoden. Möjligheterna <strong>för</strong> analys av det abstrakta syntaxträd, som JavaCC också har<br />

möjlighet att skapa, är mycket större i ett bredare språk som Java.<br />

5.2 Verktyg<br />

5.2.1 Yacc-Lex<br />

Yacc och Lex är ett av de vanligaste kompilator-kompilator verktygen, dess filer kan efter<br />

installation läggas in i projekt i t. ex Visual C++ 6.0 projekt. Det går sedan att<br />

ställa in så Visual C++ från .yac och .lex generarar C-kod av dessa innnan övrig<br />

kompilering av projektet sker. I Lex sker den lexikala analysen, i Yacc den<br />

syntaxiska och grammatiska. I Yacc går det att lägga in semantiska aktioner samtidigt<br />

som icke terminaler i grammatiken konsumeras. Ett exempel:<br />

functions : functions function {fprintf(stdout, "Func"); $$ =<br />

connectFunctions($1,$2);}<br />

| function {fprintf(stdout, "Func"); $$ = $1;}<br />

Dels kommer skärmutskrifter att ske, dels sker funktioner som är ett led i att bygga upp ett<br />

AST. Dubbla dollartecken är uttryckets vänsterled, dollartecken + siffra är vilken plats i<br />

högerledet som avses. I lexfilen så definierar man tokens <strong>för</strong> själva språket, dessa är i vissa<br />

fall intuitiva, men i vissa fall inte. Så här kan definitionen <strong>för</strong> en kommentars-token se ut t. ex:<br />

COMMENT "!""/"*([^*/]|[^*]"/"|"*"[^/])*"*"*"\n"<br />

Inte så begriplig alltså. En annan nackdel med Yacc-Lex, eller Bison-Flex som gratis<br />

varianten heter av dessa, är att såväl den genererade koden <strong>för</strong> den lexikala analysen, som den<br />

grammatiska, är tämligen obegriplig. Man är alltså nästan helt hänvisad till Yacc-filen när<br />

man vill utveckla och ändra i sin kompilator eller <strong>analysprogram</strong>. Där<strong>för</strong> övergavs ansatsen<br />

med Yacc-Lex till slut <strong>för</strong> det här examensarbetet.<br />

33


5.2.2 Javacc-Inledning<br />

Verktyget JavaCC är namngivet på grund av att det producerar fullständigt ren javakod samt<br />

att det är en kompilator-kompilator. Det är från början konstruerat av Sriram Sankar och<br />

Sreeni Viswanadha [Sankar, Viswanadha].<br />

JavaCC är ett kommandorad-baserat verktyg. Det tar en regelfil som parameter och ut får man<br />

den parser som man vill ha. Valet av kompilator-kompilator <strong>för</strong> detta examensarbete kom till<br />

slut att bli JavaCC, det har enligt min mening en enklare regelstruktur än Yacc-Lecc, samt att<br />

den allra största delen av regelfilen <strong>för</strong> <strong>Rapid</strong> var gjord redan tidigare av examensarbetarna<br />

Kalle Gustafsson och Ilja Alaoja på MDH, <strong>för</strong> en annan <strong>Rapid</strong> och ABB relaterad uppgift.<br />

[Gustafsson, Alaoja, 06].<br />

5.2.3 Regler-Input<br />

En viktig sektion i regelfilen och som ska komma tidigt är möjligheterna till olika options, ett<br />

exempel på två av dessa:<br />

options<br />

{<br />

LOOKAHEAD = 4;<br />

IGNORE_CASE = true;<br />

}<br />

LOOKAHEAD står <strong>för</strong> hur många tokens som i det här fallet <strong>Rapid</strong>kompilatorn ska se i<br />

<strong>för</strong>väg i en regelstruktur. Det är inte alltid uppenbart <strong>för</strong> en parser (som detta i grunden är)<br />

vilken väg den ska ta. Av flera möjliga vägar kan minst två inledas med samma token(s). Ju<br />

kortare parsern ska se framåt ju snabbare blir den å andra sidan. IGNORE_CASE är givetvis<br />

om språket i sina nyckelord och identifierare ska skilja på stora och små bokstäver, det gör<br />

inte <strong>Rapid</strong>. Efter det kommer en sektion i filen, innan<strong>för</strong> nyckelorden:<br />

PARSER_BEGIN(MyParser)<br />

//Javakod<br />

och:<br />

PARSER_END(MyParser)<br />

Innan<strong>för</strong> dessa nyckelord kan man skriva ren javakod. Kalle Gustafsson och Ilja Alaoja<br />

började med att generera import-fil-direktiv, vilket man i och <strong>för</strong> sig också kan lägga till i den<br />

utkommande filen MyParser.java sedan. Fördelen med att lägga till dom direkt i regelfilen är<br />

att de alltid kommer att finnas vid en ny generering av javafiler, dessa filer kommer<br />

naturligtvis inte ihåg hur dom såg ut <strong>för</strong>ra gången vilket kan leda till att man måste göra om<br />

samma saker flera gånger när man arbetar med JavaCC. Det viktigaste innan<strong>för</strong><br />

PARSER_BEGIN och PARSER_END är klassdeklarationen om själva Parsern, den ska heta<br />

likadant som i direktiven. I det här fallet kan deklarationen se ut så här:<br />

class MyParser {<br />

. . .<br />

// generated parser is inserted here.<br />

}<br />

Här kan man lägga till egen kod efter hur den egna parsern ska agera. Man får en<br />

34


konstruktorprototyp och konstruktor, i det här fallet ser den <strong>för</strong>stnämnda ut enligt:<br />

public MyParser(java.io.InputStream stream) {<br />

this(stream, null);<br />

}<br />

JavaCC kommer sedan också att skapa en rutin <strong>för</strong> varje icketerminal bland reglerna. Dessa är<br />

med andra ord de tillåtna tokens, t. ex if som inledningen på en sats, vid varje ställe i<br />

<strong>Rapid</strong>grammatiken. Den fil som åsyftas i konstruktorns parameter är den källkodsfil som ska<br />

parsas, i det här fallet i <strong>Rapid</strong>. Först nu är vid framme vid den grammatiska delen av JavaCC:s<br />

<strong>för</strong>väntade regelfil. Grammatiken, eller "Produktionen av reguljära uttryck" [CollabNet, 07]<br />

består av fyra olika delar i JavaCC, TOKEN, SPECIAL_TOKEN, SKIP och MORE. För att<br />

bygga en parser som ska klara ett språk av <strong>Rapid</strong>s svårighetsgrad räcker det med SKIP och<br />

TOKEN. SKIP är tecken och teckensekvenser som ska ignoreras. Ett exempel på en SKIPsektion<br />

som ignorerar radbrytningstecken:<br />

SKIP :<br />

{<br />

"\n"<br />

}<br />

Exempel på TOKEN, som är byggstenarna i själva språket:<br />

TOKEN :<br />

{<br />

<br />

| <br />

| <br />

}<br />

Vad vi vet om det här presumtiva programspråket är att det accepterar uttrycken TRUE och<br />

FALSE, samt att radbrytningstecken inte betyder någonting, vilket också gäller om man<br />

blandar stora och små bokstäver, samt att det kollar fyra tokens framåt vid den grammatiska<br />

produktionen. Efter den lexikala analysen är det dags <strong>för</strong> den syntaktiska, själva reglerna. Så<br />

här börjar den i <strong>Rapid</strong>fallet:<br />

Node input() :<br />

{Node n; TopNode ret; String tImage;}<br />

{<br />

{<br />

ret = new TopNode(0);<br />

tImage = getToken(2).image;<br />

}<br />

(<br />

n = moduleDeclaration(tImage)<br />

{<br />

ret.modules.add(n);<br />

tImage = getToken(2).image;<br />

}<br />

)*<br />

<br />

{ return ret; }<br />

}<br />

35


Node är <strong>för</strong>definierad i JavaCC, den är här själva roten i AST:s. input kommer verkligen att<br />

skapas som en parameterlös funktion i den utkommande javakoden. Observera hur likt<br />

JavaCC är riktig programmering. Tack vara *-tecknet så kan flera moduler hakas på topnoden.<br />

Lika intutiv är regeldefinitionen av identifierare:<br />

String identifier() :<br />

{String ret;}<br />

{<br />

..................<br />

..................<br />

return ret;<br />

..................<br />

}<br />

Endast ett fåtal regler ingår i denna JavaCC-funktion, som talar om att det ska vara token<br />

IDENT vilken i sin tur egentligen bara säger att det kan vara nästan allt annat än ett<br />

<strong>Rapid</strong>nyckelord. Tillbaka till dom boolska variablerna, som är en av tre literaler, t.ex <strong>för</strong><br />

användning i högerledet vid tilldelning:<br />

void literal() :<br />

{}<br />

{<br />

| | <br />

}<br />

literal kommer att skapas som javaprocedur, ingen programmering läggs till på JavaCC-nivån<br />

utöver kontroll av icketerminlernas automatgenererade rutiner (se samma stycke ovan). Dessa<br />

är dock inte det minsta intuitiva och näst intill omöjliga att modifiera om man vill det.<br />

Observera att typkontroll vid t. ex tilldelning, inte ligger inom den syntaktiska regelstrukturen.<br />

Så är det i alla kompilatorer vad jag vet.<br />

36


5.2.3 Output<br />

Tre javafiler kommer att skapas utifrån regelfilen. En av dessa är en sammanfattning över<br />

vilka tokens som man kan använda, en hanterar den lekikala analysen, och i en sker själva<br />

parsningen samt att AST byggs upp. Så här ser tidigare nämnda literal funktion ut den<br />

sistnämnda<br />

static final public Vector literal() throws ParseException {<br />

Vector v = new Vector();<br />

if (jj_2_61(4))<br />

{<br />

jj_consume_token(NUM_LITERAL);<br />

v.add(token.image);<br />

}<br />

else if (jj_2_62(4))<br />

{<br />

jj_consume_token(STRING_LITERAL);<br />

v.add(token.image);<br />

}<br />

else if (jj_2_63(4))<br />

{<br />

jj_consume_token(BOOL_LITERAL);<br />

v.add(token.image);<br />

}<br />

else<br />

{<br />

jj_consume_token(-1);<br />

throw new ParseException();<br />

}<br />

return(v);<br />

}<br />

Observera att den skapades (av Kalle Gustafsson och Ilja Alaoja) som en procedur, void, men<br />

eftersom dess värde var intressant <strong>för</strong> den här uppgiften ändrades det till en Vector, där olika<br />

datatyper kan läggas. Hade typkontroll varit intressant <strong>för</strong> den här parsern skulle man kunna<br />

lägga även typen i vektorn v som returneras. Icke terminalernas funktioner, t. ex jj_2_62(4), är<br />

svåra att modifiera och det rekommenderas knappast heller. Men på ett ställe har jag faktiskt<br />

styrt om vägen <strong>för</strong> dessa. Det handlar om när installerade datatyper figurerar (se avsnitt 2.3.5).<br />

Dessa finns inte specificerade i regelfilen eftersom <strong>Rapid</strong> inte anger vilka de exakt är.<br />

5.3 AST från Parser<br />

5.3.1 Skapas under parsning<br />

Det går som beskrivits i avsnitt 4.3 och 4.4 att returnera noder tillbaka vid parsning och<br />

konsumering av tokens. När ett statement konsumeras kan tolv olika noder returneras i<br />

<strong>Rapid</strong>grammatiken. Dessa har delvis samma attribut men också olika. If-noden har t. ex en<br />

Vektor med uttryck <strong>för</strong> den väg som programmet i truescenariot. Detta kan i sin tur vara andra<br />

If-noder eller andra. If-noden har liksom alla noder heltals datatypen Line, samt att dom flesta<br />

37


har även Column. Noden ProcedureCallNode skickar bland annat sina sändande parametrar<br />

medan noden RoutineDefNode skickar sina mottagande. Det är helt avgörande <strong>för</strong> analysen att<br />

ta reda på huruvida dessa är initierade.<br />

5.3.2 Analys av AST<br />

Det AST som en korrekt parsning ger, under <strong>för</strong>utsättning att rätt noder returneras vid rätt<br />

terminaler i grammatiken, är ett träd som börjar med själva programnoden. Ett enklare<br />

programspråk skulle ha sina subrutiner (och globala variabler mm) direkt under<br />

programnoden, <strong>Rapid</strong> har moduler och sedan rutiner. Det är kompilator-kompilatorprogrammerarens<br />

ansvar att varje nod innehåller tillräcklig information <strong>för</strong> att lösa den<br />

aktuella uppgiften. Man kan givetvis göra så att ny information lagras under parsning till AST.<br />

Vill man ha typkontroll t. ex så måste man observera när datatypen konsumeras och spara den<br />

i en symboltabell (se avsnitt 5.5.4).<br />

Vill man som i det här examensarbetets uppgift analysera programflöden bör man loopa fram<br />

till den rutin som ett sådant kan starta i, t. ex main. Är det istället den semantiska analysen<br />

som är intressant ska allt analyseras metodiskt från grunden. Lämpligtvis skickas hela<br />

trädstrukturen med noder till en ny separat källkodsfil, här heter den AnalyzeTree.java. Till<br />

samma fil bör även symboltabellen, som man också bör göra upp parsning, skickas. Den bör<br />

till skillnad från trädet kunna uppdateras under analysens gång. Har den kompilator, eller vad<br />

man vill göra, konfigurationsmöjligheter, ska dessa också skickas med.<br />

5.4 Analysprogrammet - Användning<br />

5.4.1 Exekvera <strong>analysprogram</strong>met från Windows (och andra operativsystem)<br />

Man kör analysverktyget <strong>för</strong> <strong>Rapid</strong>kod genom att köra filen <strong>Rapid</strong>.jar som ligger i biblioteket<br />

dist på det stället som zipfilen <strong>för</strong> det här examensarbetet packats upp. Jarfiler behandlas på<br />

samma sätt som exefiler i Windows även om körmenyn inte listar dom om man väljer<br />

"Program". Programmet tar inga inparametrar från körmenyn eftersom det har ett grafiskt<br />

användargränssnitt. Man kan givetvis också dubbelklicka på <strong>Rapid</strong>.jar i utforskaren. Självklart<br />

måste Java vara installerat på den aktuella datorn <strong>för</strong> att detta ska fungera. För att köra<br />

programmet under olika former av Unix kan man via ett terminalfönster <strong>för</strong>st bläddra fram<br />

biblioteket där <strong>Rapid</strong>.jar ligger, och sedan skriva java –jar <strong>Rapid</strong>.jar så går programmet<br />

igång.<br />

5.4.2 Konfigurera <strong>analysprogram</strong>met beträffande waitstate<br />

Öppna filen waitforcommandointraps.txt en texteditor. Den består av tre sektioner nämligen<br />

enligt exemplen:<br />

ALWAYS_WARN<br />

WaitDO<br />

WaitDI<br />

END<br />

Hittar den något kommando, procedur eller funktionsanrop, som finns med i listan i<br />

programflödesvägen så kommer varning att ges. Detta sker bara om programflödesvägen<br />

kommer ifrån en trap (se avsnitt 3.2.4). Konfigurationsfilen är inte positionsberoende<br />

38


eträffande kolumner. Parametrar eller semicolon ska inte vara med, stora eller små bokstäver<br />

är inte avgörande.<br />

WARN_IF_NOT_TIMEOUT_SETTED<br />

ReadStr<br />

ReadBin<br />

END<br />

Dessa kommandon kan accepteras i trapars programflödesväg under <strong>för</strong>utsättning att<br />

timeoutparametern är satt. Ett exempel på det: ReadBin(globNavChannel\Time:=0.5);.<br />

MAXIMUM_TIMEOUT_ACCEPT_IN_SEC<br />

0.5<br />

END<br />

Den tredje korta sektionen. Exemplet med ReadBin kommer nu att passera utan varning.<br />

5.4.3 Definiera installerade datatyper<br />

För atomiska datatyper som inte är <strong>för</strong>definierade så är det bara att lista vilka det är i filen<br />

instaleddatatypes.txt, en <strong>för</strong> varje rad, vill man kommentera något om det, inled raden med<br />

utropstecken. Om programmet ger ifrån sig ett "Parse error on line X" meddelande under<br />

körning kan det mycket väl vara <strong>för</strong> att det på den raden finns en datatyp som inte finns med i<br />

filen. I filen instaledrecorddatatypes.txt kan man lista installerade recorddatatyper, hanteringen<br />

av dess medlemmar är inte klar ännu. I instaledaliasdatatypes.txt är det meningen att man ska<br />

lista installerade aliasdatatyper, om man nu har någon sådan i sitt <strong>Rapid</strong>projekt.<br />

5.4.4 Ändra i <strong>analysprogram</strong>mets kod<br />

I Source Packages och kan man öppna filen <strong>Rapid</strong>.java i Netbeans. Denna<br />

fil innehåller det grafiska gränssnittet. I dess källkod byggs även .prg samt en .pre fil upp, den<br />

senare innehåller globala record och alias-deklarationer. I paketet bluefield finns källkoden<br />

finns resten och nästan all källkod <strong>för</strong> <strong>analysprogram</strong>met. De två största filerna är<br />

<strong>Rapid</strong>Parser.java, som sköter allt utom programflödesanalysen på AST, det sker i<br />

AnalyzeTree.java. Avsnitt 5.5 och i viss del även 5.6, ger stöd åt om man vill komma igång<br />

med egen programmering i <strong>analysprogram</strong>met, samt är givetvis i sig en del av lösningen.<br />

5.5 Analysprogrammet - Programmering<br />

5.5.1 Input till programflödesanalys<br />

Input till programflödesanalysen i filen AnalyzeTree.java är fram<strong>för</strong> allt noden tree av typen<br />

TopNode. Åtkomst till modulerna i den finns via .modules. Konstruktorn i filen visar hur man<br />

därefter når rutinerna i dessa. Vidare kan man se hur uttrycken och variabler mm i dessa.<br />

Observera att det är fritt fram att skapa räknare över såväl moduler, rutiner, uttryck osv, fast<br />

man är inom en redan existerande loop, <strong>för</strong>utsatt att en ny loopvariabel används givetvis.<br />

Övrig intressant information till programflödesanalys beskriver sig själva i AnalyzeTree:s<br />

konstruktor.<br />

39


5.5.2 Detektering av waitstate<br />

Endast de rutiner som är trapar, eller som är anropade i en orsakskedja utifrån en trap, är<br />

intressanta här. Information om de är connectade, vilka endast kan vara intressanta vid en<br />

inställning i programmet, finns redan. Uttryck efter uttryck bearbetas genom<br />

checkcommandnode, här är scenariorna med procedurecallNode och functioncallNode<br />

inressanta. De intressanta riskkommandona som beskrivs i avsnitt 1.4 är ju sådana anrop till<br />

digitala anrop. observera dock att de sistnämnda även kan ligga inom assignnoden. Självklart<br />

kan dessa även finnas inom t. ex while:s satser, men de har redan anropats från den via<br />

checkcommandnode då. Känns namnet på rutinanropet igen med de väntekommandon som<br />

varnats <strong>för</strong> blir det en varning (se avsnitt om Konfigurera <strong>analysprogram</strong>met beträffande<br />

waitstate).<br />

5.5.3 Detektering av oinitierade variabler<br />

I checkcommandnode:s kontroll av exempelvis assignnoden så ser vi att assignnodens<br />

högerled består av en vektor v där såväl noder, uttryck, literaler som identifierare kan finnas.<br />

För att ta identifierarna som exempel så kollas att inga sådana används om de inte är<br />

markerade som initierade i symboltabellen. Notera att här kan <strong>analysprogram</strong>met stöta på<br />

såväl parametrar som lokala som globala variabler, och dessa kan inte behandlas på precis<br />

samma sätt, mer om detta senare.<br />

5.5.4 Symboltabeller – Beskrivning<br />

Symboltabeller används i alla kompilatorer och <strong>för</strong>modligen alla analysverktyg som detta. De<br />

innefattar, liksom här, såväl rutiner som variabler etc. I kompilatorer är en av<br />

huvudanledningarna typkontroll, här är det att hitta oinitierade variabler. I många kompilatorer<br />

skapar man en ny symboltabell <strong>för</strong> varje rutin, samt en global. Här skapas inte nya<br />

symboltabeller på det kriteriet, utan istället kan den dela sig i flera om det dyker upp separata<br />

programflödesvägar, mer om detta i avsnitt 3.2.2. Symboltabellen i <strong>analysprogram</strong>met,<br />

klassen SYmbolTable, innehåller fram<strong>för</strong> allt medlemmarna Symbol i en länkad lista, denna<br />

har följande privata medlemmar:<br />

global (boolean)<br />

block (string);<br />

type (string);<br />

name (string);<br />

assigned (boolean);<br />

value (value);<br />

line (int);<br />

I filen SymbolTable.java finns en rad funktioner <strong>för</strong> att lägga till symboler, läsa utifrån olika<br />

sökkriterier mm. Den enklaste rutinen är konstruktorn som helt enkelt skapar en tom länkad<br />

lista med symboler.<br />

5.5.5 Lokala symboler <strong>för</strong>e globala<br />

Funktionen getSymbol finns i två ut<strong>för</strong>anden, en som tar den boolska parametern global och<br />

en utan. I den senare kollas <strong>för</strong>st om den angivna symbolen finns och är lokal, d v s blocket<br />

stämmer överens med det som man skickar in, om inte, kollas efter globala, d v s blocket har<br />

ingen betydelse. Detta eftersom lokala variabler i <strong>Rapid</strong>, går <strong>för</strong>e, eller gömmer, globala.<br />

Samma gäller i funktionen setSymbolAssigned i SymbolTable.java.<br />

40


5.5.6 Variabel eller parameter<br />

I en lokal <strong>Rapid</strong>rutin kan <strong>analysprogram</strong>met lika gärna stöta på en inskickad parameter som en<br />

lokalt eller globalt deklarerad variabel. Jag har valt att lösa det genom att om symboltabellen<br />

inte finner denna symbol initierad så kollar det i t. ex assignnodens scenario i AnalyzeTree<br />

genom funktionen isparameterandassigned om det kan vara en initierad parameter. Den<br />

<strong>Rapid</strong>rutin som programflödesvägen nått fram till har fått en struktur<br />

SendingRoutineParameters skickad till sig, om den inte är null finns där lagrat information om<br />

den sändande rutinens parametrar och om dessa är initierade.<br />

5.5.7 Recordar i symboltabellen<br />

Recordar komplicerar bilden en aning när man söker efter oinitierade variabler, inte minst om<br />

de skickas som parametrar. Jag har valt att lösa det så att dels kan en hel record existera i<br />

symboltabellen, och den kan vara markerad som initierad där. Men om den eftersöks och inte<br />

finns så kan även enstaka recordmedlemmar existera där, och dessa kan ha initierats. Ska t. ex<br />

en lokal instans av en record tilldelas en global kollar <strong>analysprogram</strong>met <strong>för</strong>st om hela den<br />

finns som initierad i symboltabellen, om inte söker den upp varje medlem i denna och är alla<br />

initierade var <strong>för</strong> sig, t.ex genom tilldelning, så är det okej. Det omvända scenariot finns också<br />

att en lös recordmedlem med punktoperatorn t. ex finns i högerledet av en assignsats.<br />

Analysprogrammet kollar då <strong>för</strong>st om hela recorden finns som initierad, om inte så om just<br />

den recordmedlemmen är det.<br />

5.5.8 Behov av ny symboltabell i programflödesvägen<br />

Detta behov uppstår t. ex när en if-node dyker upp i programflödesvägen. Innan<br />

<strong>analysprogram</strong>met träder in i den så sparar den symboltabellen som den ser ut innan på en fil<br />

som indikerar att det är en "if-symboltabell" samt att radnumret ingår i filnamnet också. Att<br />

spara den i en variabel hade varit mer praktiskt, men på grund av "call by reference" problem,<br />

(se avsnitt 1.7) i Java så hade den gamla symboltabellen hela tiden en tendens att uppdateras<br />

även den i det som nu beskrivs. In<strong>för</strong> If-nodens olika vägar, true samt eventuellt else och olika<br />

else if villkor så börjar man på nytt med den gamla symboltabellen.<br />

5.5.9 Test av flera symboltabeller beträffande en symbols värde<br />

Genom att deklarera en instans av klassen SymbolTableVector och lägga alla uppdaterade<br />

symboltabeller (en <strong>för</strong> varje programflödesväg i t. ex if-satsen) och sedan genom dess<br />

medlemsfunktion makeonesymboltable så summeras vad som har hänt totalt i alla if-satsens<br />

delar. Om t. ex en variabel tilldelats i alla så ska den också markeras som initierad efter det att<br />

if-satsen avverkats. Denna kontroll sker på globala variabler samt variabler som finns just i<br />

den aktuella subrutinen där if-satsen finns.<br />

5.6 Felmeddelanden och varningar<br />

5.6.1 Felmeddelanden beträffande lexikal analys<br />

Som belyses i avsnitt 5.2.3 så kan <strong>analysprogram</strong>met träda in i kodraden: throw new<br />

ParseException(); om den inte lyckas konsumera någon av icketerminalerna innan. Någon av<br />

konstruktorerna i den av JavaCC producerade filen ParseException.java kommer att köras<br />

beroende på vilken information som finns tillgänglig vid felet i <strong>Rapid</strong>koden. Ett vanligare<br />

händelse<strong>för</strong>lopp är att när <strong>analysprogram</strong>met <strong>för</strong>söker konsumera en eller flera icketerminaler,<br />

och misslyckas, så konsumerar den en Token genom att köra funktionen jj_consume_token i<br />

<strong>Rapid</strong>Parser.java med ingångsvärdet -1. När detta inte lyckas så körs i sin tur<br />

41


generateParseException vilken bygger ihop en mer fullständig bild av Token, <strong>för</strong>väntad<br />

Token och radnummer. Detta går att använda i ParseException.java, som nu mest är ett skal,<br />

om man är ute efter att göra en mer fullödig kompilator.<br />

5.6.2 Varningar beträffande waitstateanalys<br />

Genom att deklarera en instans av klassen GetTrapTimeWarningCommands som läser in den<br />

editerbara filen waitingcommandosintraps.txt så får <strong>analysprogram</strong>met information genom två<br />

vektorer och en float (vilken är högst tillåtna väntetid <strong>för</strong> vissa kommandon) om vilka<br />

kommandon som den ska varna <strong>för</strong> om de dyker upp i trapars programflödesväg. Klassen<br />

Writeerrormessage skriver felmeddelanden men exakt samma felmeddelande (text, rad samt<br />

eventuell kolumn) kan inte <strong>för</strong>ekomma två gånger.<br />

5.6.3 Varningar beträffande oinitierade variabler<br />

En <strong>för</strong>utsättning <strong>för</strong> att programmet ska undersöka om en variabel, persistance etc är oinitierad<br />

är <strong>för</strong>st och främst att man verkligen har stött på en variabel. Först undersöker programmet om<br />

det nått en nod av något slag istället, t. ex en funktionsanrops-nod. Observera att det i den<br />

funktionsanrops-noden kan finnas variabler att undersöka i en eventuell parameterlista, men<br />

den kontrollen sker så att säga ett varv senare.<br />

Därefter går isVarPersConstAssigned i SymbolTable.java igång. SymbolTable har givetvis<br />

inget problem med att rätt symboltabell används eftersom det är en instans av denna som<br />

används. En instans av GetRecordAndAliasDatatypes.java skapas <strong>för</strong> i den finns strukturer<br />

med deklarerade recordar i <strong>Rapid</strong>progammet. Dessa var tvungna att skapas innan som ett slags<br />

<strong>för</strong>kompilering. Dessutom kollas vilka installerade datatyper som finns (se avsnitt 2.3.5).<br />

Dessa strukturer kan man få nytta av om det inte är en vanlig atomisk variabel, programmet<br />

kollar nämligen om det är så att hela recordinstansen är markerad som initierad om det är så<br />

att den eftersökta recordmedlemmen inte är det, då är allt okej och ingen varning kommer att<br />

ges.<br />

Dessutom kan det omvända scenariot gälla, att man stött på en instans av en hel<br />

recordstruktur, och den inte finns i symboltabellen som initierad, men alla dess medlemmar är<br />

markerade som det. Då kommer ingen varning att ges <strong>för</strong> att recordvariabeln är oinitierad.<br />

Återigen skriver klassen Writeerrormessage felmeddelandet om den ska det.<br />

5.6.4 Varningar beträffande oinitierade parametrar<br />

Om ovanstående misslyckas kan det bero på man stött på en parameter, symboltabellen<br />

uppdaterar inte per automatik sådana vid t. ex funktionsanrop. Funktionen<br />

isparameterandassigned i AnalyzeTree.java går igång och undersöker en instans av strukturen<br />

SendingRoutineParameters (se avsnitt 4.4.4) där information finns om den sändande rutinens<br />

motsvarande variabels tillstånd. Inte bara atomiska variabler etc undersöks utan även recordar,<br />

på samma sätt som beskrivits i <strong>för</strong>egående avsnitt.<br />

5.6.5 Tillvägagångssätt i motttagande rutin<br />

Isparameterandassigned i AnalyzeTree.java har givetvis namnet på parametern som den ska<br />

undersöka. Den har även som framgår av parameterlistan en vektor parameterlist med<br />

<strong>Rapid</strong>parametrar <strong>för</strong> den mottagande rutinen i den ordning som dom är deklarerade. Nu kan<br />

den aktuella <strong>Rapid</strong>parametern få ett nummer som kan matchas mot motsvarande i strukturen<br />

som är en instans av SendingRoutineParameters, och huruvida den hann bli initierad innan<br />

den skickades vidare via funktions eller proceduranrop.<br />

42


5.6.6 Sändande funktioner, procedurer och trapar<br />

Innan ett funktionsanrop börjar stega igenom funktionen så byggs en instans av strukturen<br />

SendingRoutineParameters upp. Som framgår av dess medlemsfunktioner så kan såväl<br />

atomiska datatyper, och huruvida dom är initierade eller ej, samt recordstrukturer läggas till i<br />

den. Observera att funktionsanropsnoden, där detta sker just nu, också måste undersöka om<br />

huruvida det är en egen inparameter som ska skickas, och inte bara anta att det är en variabel.<br />

Den är då i sig att betrakta som en mottagande rutin. Inte bara instansen till<br />

SendingRoutineParameters skickas med till stepthrougfunction i AnalyzeTree, utan också<br />

symboltabellen vid det aktuella tillfället.<br />

5.7 Orsakskedjor<br />

Som nämnts i <strong>för</strong>egående avsnitt kan den sändande funktionen i sin tur vara en mottagande<br />

funktion osv. Dessa anropskedjor kan vara hur långa som helst. För att kunna blicka bakåt i<br />

orsakskedjan så har den struktur som kopplar samman mottagande och sändande rutiners<br />

parametrar, SendingRoutineParameters, en egen instans av sig själv. Är den inte null när den<br />

kontrolleras, samt att det finns ett riktigt rutinnamn, så ger <strong>analysprogram</strong>met <strong>för</strong>slag på i<br />

vilka rutiner som den sändande parametern kan behöva initieras. Analysprogrammet vet om<br />

huruvida en parameter är oinitierad eller ej, även långt fram i orsakskedjan, fast det säger inte<br />

exakt i vilken rutin i kedjan som den behöver initieras i, utan ger som sagt <strong>för</strong>slag på detta.<br />

5.8 Skal och vidareutveckling <strong>för</strong> andra uppgifter<br />

Som beskrivits i avsnitt 1.3 är detta program inte ett skal och inte byggt <strong>för</strong>st som ett skal <strong>för</strong><br />

att sedan låta just implementeringen av sökandet efter oinitierade variabler och vänteläge i<br />

trapar, ta vid. Men det är ändå ganska bra strukturerat och flera nyttiga klasser och<br />

programstrukturer går att använda <strong>för</strong> att lösa andra uppgifter också. T. ex är det inte svårt att<br />

presentera felmeddelanden, symboltabellerna innehåller medlemmar <strong>för</strong> värden, även om<br />

dessa inte direkt används nu. I AnalyzeTree.java finns flera självbeskrivande funktioner och<br />

procedurer även om det också är långa loopar med flera villkor. Det är bra om man följer<br />

indenteringen med nedåtpil-tangenten vid dessa, <strong>för</strong> att komma till rätt nivå. Alla uppgifter <strong>för</strong><br />

ett statiskt <strong>analysprogram</strong>, vilka det finns exempel på i avsnittet ”Relaterat arbete” nedan,<br />

bygger dessutom på metoden med programflödesanalys på ett upprättat abstrakt syntaxträd,<br />

AST, vilket således naturligtvis finns tillgängligt även i detta <strong>analysprogram</strong>.<br />

43


6. Relaterat arbete<br />

6.1 Inledning<br />

Det finns ett antal statiska <strong>analysprogram</strong> <strong>för</strong> kontroll av källkods korrekthet, <strong>för</strong> bland annat<br />

C, C++ och Javakod. Tre av dessa presenteras i varsitt avsnitt nedan. Informationen är<br />

hämtad från ”White paper” från respektive <strong>för</strong>etags hemsida. Som en introduktion till det här<br />

examensarbetet läste jag även översiktligt rapporter från Stanford University, som också givit<br />

upphov till Coverity, som beskrivs i 6.2. Dessa handlade bland annat om hur stora<br />

operativsystem (t. ex Linux) behandlar resurser som är gemensamma <strong>för</strong> hela systemet. Dessa<br />

kan behöva ha lås, och då gäller det att hålla reda på om detta är satt eller inte, och om det är<br />

rätt i så fall. Metoden är inte helt olik den som <strong>för</strong> att hitta oinitierade variabler i det här, och<br />

följande refererade arbeten.<br />

6.2 Coverity<br />

6.2.1 Historik<br />

Coverity är ett <strong>för</strong>etag som kommit med ett antal kommersiella verktyg <strong>för</strong> statisk analys av<br />

C/C++ kod. Det började egentligen som en del i att få fram mer säker och kvalitativ kod på<br />

Stanforduniversitetet och blev från och med 2002 ett eget kommersiellt <strong>för</strong>etag. Bland de som<br />

använder Coveritys verktyg finns bland annat NASA, McAfee, Palm och Sun.<br />

6.2.2 Statisk analys av C/C++ program<br />

Coveritys verktyg är kan man säga är en parallell kompilator till den som koden ligger i. Den<br />

är helt oberoende av denna och det går att installera Coverity till många olika C/C++<br />

kompilatorer och plattformar, och fram<strong>för</strong>allt är det enkelt att installera Coverity, kanske <strong>för</strong><br />

att det är helt oberoende av miljö och kompilator. ”Coverity Prevent begins its analysis by<br />

actually compiling the code, using the same process to understand the code that a compiler<br />

uses to generate object files and executables.” [Coverity05] (s. 14) Genom att kompilera<br />

koden <strong>för</strong>st undviker Coverity de ofta felaktiga varningar, och ännu värre, missade varningar,<br />

som andra statiska verktyg kan ge upphov till.<br />

6.2.3 AST-struktur<br />

Coverity skapar inte oväntat ett abstrakt syntaxträd, AST, samt flödesgrafer och anropsgrafer.<br />

Det resulterar sedan i ett VBE (Virtual Environment Build) som i sin tur resulterar i en<br />

funktionsmodell. Efter att onödiga delar av funktionsmodellen tagits bort, det kan t. ex vara<br />

rutiner som aldrig anropas, så börjar analysen.<br />

6.2.4 Interprocedurell granskning<br />

Felaktig och riskabel kod sträcker sig ofta över flera olika rutiner, men det är inte omöjligt att<br />

analysera när man har ett AST och som i det här fallet en funktionsmodell. Se följande<br />

exempel från Coveritys hemsida:<br />

44


100 void buggy(char *p) {<br />

101 my_free(p, 1);<br />

102 *p = ‘\0’;<br />

103 }<br />

104<br />

105 static int total_alloc;<br />

106<br />

107 void my_free(void *p, int sz) {<br />

108 if(sz > 0)<br />

109 free(p);<br />

110 total_alloc -= sz;<br />

111 }<br />

[Coverity05] (s. 16)<br />

Coverity ser att att proceduren buggy anropar my_free och att den andra inparametern är 1.<br />

Den kommer då att gå in i truedelen på rad 109 och stryka den andra inparametern pekaren p<br />

ur minnet, tyvärr så används den senare i buggy på rad 102. Coverity kommer att rapportera<br />

det här felet, fast hade 0 varit den andra parametern på rad 101 hade inget fel rapporterats.<br />

6.2.5 Egenskaper och fel som upptäcks<br />

Coveritys verktyg upptäcker bland annat användning av oinitierade variabler, deadlocks, dvs<br />

två uppgifter, t.ex i två olika trådar väntar på varandra och ingen blir gjord, användning av<br />

pekare som avallokerats, att free av pekare körs två gånger och felaktig allokering av minne<br />

mm.<br />

6.2.6 Likheter med <strong>Rapid</strong> <strong>analysprogram</strong>met<br />

En av de egenskaper som Coveritys verktyg kollar efter är som nämnts oinitierade variabler.<br />

Precis som <strong>Rapid</strong>-<strong>analysprogram</strong>met så bygger det upp ett abstrakt syntaxträd och tar sedan ut<br />

programflödesvägar ur det. De anropsgrafer som nämns ovan är kan sägas vara en del av<br />

programflödesvägarna här. Den databas över fel som verktygen kan hitta har en viss, men<br />

begränsad likhet i den editerbara fil som finns över vilka kommandon som inte alls, eller<br />

under vissa omständigheter inte bör, komma i trapars programflödesväg, se avsnitt 3.2.4.<br />

6.2.7 Skillnader gentemot <strong>Rapid</strong> <strong>analysprogram</strong>met<br />

Coverity är inriktad på C/C++ kod vilken inte är över<strong>för</strong>bar till <strong>Rapid</strong>. Dess verktyg Coverity<br />

prevent och Coverity extend är inriktat på betydligt fler programmeringsfel med undantaget att<br />

de inte tar ut speciella programflödesvägar <strong>för</strong> event (i <strong>Rapid</strong> trap) och särbehandlar vissa<br />

kommandon där. Coverity kan eliminera vissa programflödesvägar om dess uppställda villkor,<br />

t. ex if-satser, aldrig kan uppnås. <strong>Rapid</strong>-<strong>analysprogram</strong>met analyserar alla programflödesvägar<br />

och tar inte ställning till hur villkoren ser ut <strong>för</strong> dessa.<br />

6.3 Klocwork<br />

6.3.1 Likheter med Coverity<br />

Klocwork bygger liksom Coverity upp ett abstrakt syntaxträd med noder över kommandon.<br />

Precis som Coverity kan det utvärdera om vissa programflödesvägar är möjliga att ta <strong>för</strong> att<br />

sedan utesluta andra. [Fisher,07] (sida 4). Klocworks analysverktyg går att applicera på C/C++<br />

men också Java.<br />

6.3.2 Fel som upptäcks<br />

6.3.2.1 Denial of Services<br />

Detta gäller serverprogram, <strong>för</strong>ut kanske DCOM Automation Object, numera kanske oftare<br />

.NET services eller Java-servlets. Funktioner som kan nås via t. ex Internet kan missbrukas <strong>för</strong><br />

45


att överbelastas serverdatorn. Detta t. ex genom att vektorer som skickas som inparametrar<br />

inte avallokeras under alla omständigheter, varefter minnet tar slut.<br />

6.3.2.2 Minnesfel<br />

Nullpekare får inte under några omständigheter behandlas som om de vore inte vore det.<br />

Ibland är det inte uppenbart att en pekare har avallokerats och satt till NULL i C/C++. Det kan<br />

t. ex ske genom att en annan pekare tilldelats den pekare som sedan anropas, och den<br />

<strong>för</strong>stnämnda har satts till NULL innan. Den typen av fel upptäcks av Klocwork.<br />

6.3.2.3 Fel i arrayer och vectorer<br />

Följande exempel från Klocworks WhitePaper får illustrera:<br />

void f(unsigned char* stream)<br />

{<br />

unsigned char buf[32];<br />

memcpy(buf, stream + 1, *stream);<br />

…<br />

}<br />

”In this trivial case, the author has made a fundamental assumption about the cleanliness of<br />

the incoming data, coupled with an architectural assumption about the range of that data. If<br />

this function is used in an environment open to attack, for example to process marshaled data<br />

from another process or server, or even from a file that is subject to injection on the user’s<br />

system, the attacker could cause considerable stack corruption simply by exploiting the fact<br />

that the code will happily copy up to 255 bytes into a buffer able to hold only 32” [Fisher,07]<br />

(s. 7) Med detta exempel syftar Fisher på en svaghet i ett serverprogram som kan utnyttjas av<br />

en hackare <strong>för</strong> att krascha programmet eller i värsta fall datorn. Men det är lika illa om det<br />

finns i ett vanligt program på den lokala datorn eftersom andra programmerare i ett projekt,<br />

eller samma person, kan utnyttja proceduren ofrivilligt på fel sätt. Klocworks analysverktyg<br />

upptäcker alltså i vilket fall programmeringsfelet.<br />

6.4 Polyspace<br />

6.4.1 Inledning<br />

Många av Polyspace:s kunder finns inom fordonsindustrin. Bilar mm innehåller många<br />

datorsystem och program, oftast skrivna i C. Polyspace motiverar sitt verktyg och sina<br />

rekommendationer <strong>för</strong> god programmering gentemot dessa med att kostnaderna <strong>för</strong> att testa Ckoden<br />

annars skulle bli mycket högre, samt att säkerheten <strong>för</strong> passagerarna i motorfordonet<br />

kommer att bli högre med bättre och säkrare kod i systemen.<br />

6.4.2 Likheter med Coverity och Klocwork<br />

Polyspace kallar sitt verktygs metod <strong>för</strong> semantisk analys. ”Semantic Analysis relies on a wide<br />

base of mathematical theorems that provide rules for analyzing complex dynamic systems<br />

such as software applications” [Hote,01]. Polyspace hittar genom dessa precis som Coverity<br />

oinitierade variablers användning och som Klocwork möjliga fel i arrayer och vektorer, och<br />

dessutom tack vare ”worst case scenario” analys möjliga overflow och underflow i datatyper.<br />

Självklart genom<strong>för</strong> det en interprocedurell analys eftersom programflödesvägar sträcker sig<br />

genom dessa i t. ex C.<br />

6.4.3 Underflow och Overflow i datatyper<br />

46


Signed och Unsigned datatyper agerar helt olika om dess värde överstiger max <strong>för</strong> datatypen,<br />

t.ex heltal. Unsigned ger ju ingen information om dess värde är negativt eller positivt, alla<br />

<strong>för</strong>utsätts vara positiva. En osignerad datatyp får värdet 0 vilket kan vara en indikator sedan på<br />

att något gått fel, medan en signerad börjar om i sin negativa ände vilket kan ge upphov till<br />

svåra fel senare i programmet. Det är fritt fram att blanda signerade och osignerade datatyper i<br />

C, och dessutom kan man efter en matematisk operation spara resultatet från en eller flera<br />

större datatyper i en mindre, vilket i sin tur kan ge programkörningsfel senare i programmet.<br />

Polyspace:s verktyg kan hjälpa till att skapa säkrare behandling mellan olika datatyper:<br />

”As opposed to Ada, strong typing is not part of C, but adopting type checking rules helps maintain a<br />

clearer design” [Lalo, Barriault,05]. Detta är också något som Motor Industry Software<br />

Reliability Association, MISRA, har i sina riktlinjer. Polyspace ut<strong>för</strong> också ”värsta tänkbara<br />

scenario” beträffande vilka värden signerade och osignerade datatyper kan få i ett C-program.<br />

Som beskrivits tidigare kan särskilt signerade datatyper skapa fatala problem vid overflow.<br />

6.4.4 Division med 0<br />

Division med 0 är inte tillåtet inom programmering. Ett uttryck som t. ex:<br />

A = x / (x-y);<br />

[Deutsch, 03] är riskabelt vilket Polyspace Verifier rapporterar. Ett villkor om x skiljt från y<br />

bör läggas till.<br />

47


SLUTSATSER<br />

Att utveckla ett statiskt <strong>analysprogram</strong> <strong>för</strong> programspråket <strong>Rapid</strong> visade sig vara fullt möjligt.<br />

Ibland nämns liknande verktyg <strong>för</strong> t. ex C-kod som statiska verktyg <strong>för</strong> dynamisk kod, vilket<br />

<strong>för</strong>klarar begreppet något. Analysprogrammet har samma metod som de som rapporten<br />

refererar till i det relaterade arbetet. Ett abstrakt syntaxträd byggs upp bestående av noder <strong>för</strong><br />

kommandon och olika programflödesvägar, i det här fallet alla nåbara, testas på detta. De<br />

eventuella programkörningsfel som <strong>analysprogram</strong>met i det <strong>för</strong>sta skedet skulle, och är,<br />

inriktat på är att upptäcka är:<br />

Om <strong>Rapid</strong>-programmen riskerar att gå i tidsbestämt eller ej tidsbestämt vänteläge om en trap,<br />

att jäm<strong>för</strong>a med event, är starten i programflödesvägen, samt att upptäcka om oinitierade<br />

variabler används. Det är fullt möjligt att utveckla <strong>analysprogram</strong>met så att det upptäcker<br />

andra, ej önskade egenskaper, i <strong>Rapid</strong>-programmen också.<br />

Att upptäcka trapars vänteläge visade sig vara relativt enkelt. Att upptäcka oinitierade<br />

variabler var inte så svårt heller så länge det handlade om atomiska datatyper, t. ex i <strong>Rapid</strong><br />

num, bool och string. Sammansatta datatyper kom att komplicera uppgiften, samt när<br />

variabler skickas som parametrar mellan rutiner. Inte heller detta har emellertid varit omöjligt<br />

att genom<strong>för</strong>a, dock så är inte test av olika elements initiering i arrayer implementerat i detta<br />

verktyg. Analysprogrammet hittar de egenskaper som de söker efter, av nämnda anledning<br />

med arrayer samt att vissa rekursiva anrop kan exkluderas, kanske möjligtvis dock inte till 100<br />

procent i alla <strong>Rapid</strong>projekt. De mer omfattande arbeten som refereras till i det relaterade<br />

arbetet, utger sig inte heller <strong>för</strong> att göra det.<br />

Något som kan tyckas vara ett problem vid testning av fram<strong>för</strong> allt ett riktigt <strong>Rapid</strong>projekt av<br />

filer, är att analys av detta tar ganska lång tid, i det här fallet över tre minuter på en normal<br />

dator. Detta beror bland annat på att programflödesvägarna är väldigt många och långa i det.<br />

En annan anledning kan vara att <strong>analysprogram</strong>met, efter inläsning av en regelfil i kompilatorkompilator<br />

verktyget JavaCC, är skrivet i Java. Samt givetvis att min egen programmering<br />

och analys av det abstrakta syntaxträdet, kanske i alla lägen inte är optimal. I en tidigare<br />

version av analysprogammet så tog detta <strong>Rapid</strong>projekt dock den dubbla tiden att analysera, så<br />

jag har lagt ner tid på att <strong>för</strong>söka effektivisera analysprocessen.<br />

Analysprogram av källkod kräver per definition mycket datorkraft, det är där<strong>för</strong> som<br />

uppkomsten av dessa har dröjt så länge som fram till 2000-talet, <strong>för</strong> själva algoritmen och<br />

metoden <strong>för</strong> dessa har funnits ända sedan 1970-talet. Men de orsaker som nämnts i <strong>för</strong>egående<br />

stycke kan också bidra extra till den tid som analys av ett stort och komplext <strong>Rapid</strong>projekt kan<br />

ta. Detta verktyg kanske i vissa fall passar bäst att använda vid en slutlig kontroll av <strong>Rapid</strong><br />

programmerade projekt, så kallade TASK:s.<br />

48


REFERENSER<br />

[<strong>Rapid</strong> overview] “ RAPID_overview.pdf”, “Revision C”<br />

[<strong>Rapid</strong> kernel reference] “RAPID_kernel_reference.pdf” “Revision C”<br />

[Aho, Sethi , Ullman, 88] Alfred V.Aho, Ravi Sethi, Jeffrey D.Ullman,<br />

“Compilers”, Addison-Wesley, 1988.<br />

[Gustafsson, Alaoja, 06] Kalle Gustafsson, Ilja Alaoja, “Lastanalysator till<br />

<strong>Rapid</strong>”, C-uppsats, <strong>Mälardalens</strong> <strong>högskola</strong>, IDE,<br />

Västerås<br />

http://www.mdh.se/ide/eng/msc/index.php?choice=sh<br />

ow&id=0495 , 2006<br />

[Sankar, Viswanadha] Sriram Sankar, Sreeni Viswanadha, JavaCC,<br />

https://javacc.dev.java.net/,<br />

http://www.cs.stanford.edu/~sankar,<br />

http://www.cs.albany.edu/~sreeni<br />

[CollabNet, 07] CollabNet ,”JavaCC [tm] Grammar Files“,<br />

,<br />

2007.<br />

[Coverity, 05] Coverity, ”Coverity_tech_whitepaper”, San<br />

Francisco, USA, , 2005.<br />

[Fisher, 07] Gwyn Fisher, Klocwork Inc,<br />

”AutomatedSourceCodeAnalysis.pdf”, (s. 4),<br />

, 2007.<br />

[Hote, 01] Chris Hote, General Manager at Polyspace<br />

Technologies Inc, ”Semantic_Analysis”, (s. 5),<br />

, 2001.<br />

[Lalo, Barriault, 05] Marc Lalo, Steve Barriault, Polyspace Technologies<br />

Inc,”PolySpace-white-paper-automotive”, (s. 5),<br />

, 2005.<br />

[Deutsch, 03] Alain Deutsch, Chief Technical Officer at Polyspace<br />

Technologies Inc,” Static_Verification”, (s. 4),<br />

, 2003.<br />

49


APPENDIX<br />

for(i=0;i


}<br />

}<br />

}<br />

else if command is compactif then<br />

{<br />

Compactif compactif = routine.command.get(k);<br />

compactifscenario(compactif, symboltable, actual_routinename);<br />

}<br />

//End of “compactif” scenario, now functioncall scenario<br />

else if command is functioncall then<br />

{<br />

Functioncall functioncall = routine.command.get(k);<br />

functioncallscenario(functioncall, symboltable, actual_routinename);<br />

}<br />

//End of functioncall scenario, now procedurecall<br />

else if command is procedurecall then<br />

{<br />

Procedurecall procedurecall = routine.command.get(k);<br />

procedurecallscenario(procedurecall, symboltable, actual_routinename);<br />

}<br />

//End of functioncall scenario, now goto scenario<br />

else if command is goto then<br />

{<br />

Goto goto = routine.command.get(k);<br />

symboltable = gotolabel(goto.labelname, symboltable,<br />

actual_routinename);<br />

//This function searches for a label command associated with the<br />

//name goto.labelname, and steps the code there until routine<br />

//is ended.<br />

}<br />

//End of goto scenario, now raise scenario<br />

else if command is raise then<br />

{<br />

Raise raise = routine.command.get(k);<br />

Symboltable savedsymboltable = symboltable.save;<br />

for(l=0;l>= raise.statementlist.size();l++)<br />

{<br />

Command command = raise.statementslist.get(l);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

gotoerror(symboltable);<br />

//This procedure searches for a error command in the same routine<br />

//and runs it to routine ends.<br />

symboltable = savedsymboltable; //The changes of initiations are not<br />

//safe.<br />

}<br />

51


void assignscenario(Assign assign, Symboltable symboltable,<br />

String actual_routinename)<br />

{<br />

errorfound := false;<br />

for(l=0;l


void whilescenario(While while, Symboltable symboltable,<br />

String actual_routinename)<br />

{<br />

for(l=0;l>=while.condition.size();l++)<br />

{<br />

conditionpart = while.condition.part.get(l);<br />

//Here every part of conditionpart of the expression is checked<br />

//Could be almost anything in there.<br />

if conditionpart is literal then<br />

{<br />

//No problem!<br />

}<br />

else if conditionpart is identifier then<br />

{<br />

if check_if_assigned(conditionpart, symboltable,<br />

actual_routinename) then<br />

//No problem!<br />

else<br />

report_error(conditionpart, actual_routinename);<br />

}<br />

else if conditionpart is function then<br />

{<br />

if step_through_function(functionname*, symboltable) then<br />

//No problem!<br />

else<br />

report_error(functionname*,<br />

actual_routinename);<br />

}<br />

}<br />

//End of while condition checks. Now the body<br />

Symboltable savedsymboltable = symboltable.save;<br />

for(l=0;l>=while.statementslist.size();l++)<br />

{<br />

Command command = while.statementslist.get(l);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

symboltable = savedsymboltable;<br />

//End of while statements body checks<br />

}<br />

53


void forscenario(For for, Symboltable symboltable,<br />

String actual_routinename)<br />

{<br />

symboltable.assign(for.loopvariable.name, actual_routinename);<br />

for(l=0;l>=for.from.size();l++)<br />

{<br />

frompart = for.from.get(l);<br />

//Here every part of “from” is checked<br />

//Could be almost anything in there.<br />

if frompart is literal then<br />

{<br />

//No problem!<br />

}<br />

else if frompart is identifier then<br />

{<br />

if check_if_assigned(frompart, symboltable,<br />

actual_routinename) then<br />

//No problem!<br />

else<br />

report_error(frompart, actual_routinename);<br />

}<br />

else if frompart is function then<br />

{<br />

if step_through_function(functionname*, symboltable) then<br />

//No problem!<br />

else<br />

report_error(functionname*,<br />

actual_routinename);<br />

}<br />

}<br />

//End of from checks. Now the “to” part of for expression<br />

for(l=0;l>=for.to.size();l++)<br />

{<br />

topart = for.to.get(l);<br />

//Here every part of “to” is checked<br />

//Could be almost anything in there.<br />

if topart is literal then<br />

{<br />

//No problem!<br />

}<br />

else if topart is identifier then<br />

{<br />

if check_if_assigned(topart, symboltable,<br />

actual_routinename) then<br />

//No problem!<br />

else<br />

report_error(topart, actual_routinename);<br />

}<br />

54


}<br />

else if topart is function then<br />

{<br />

if step_through_function(functionname*, symboltable) then<br />

//No problem!<br />

else<br />

report_error(functionname*,<br />

actual_routinename);<br />

}<br />

}<br />

//End of “to” checks. Now the “step” part of for expression<br />

if check_if_assigned(stepvariable.name, symboltable, actual_routinename)<br />

then<br />

//No problem!<br />

else<br />

report_error(stepvariable.name,<br />

actual_routinename);<br />

//End of for header checks. Now the body<br />

Symboltable savedsymboltable = symboltable.save;<br />

for(l=0;l>=for.statementslist.size();l++)<br />

{<br />

Command command = for.statementslist.get(l);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

symboltable = savedsymboltable;<br />

//End of for statements body checks<br />

55


void testscenario(Test test, Symboltable symboltable,<br />

String actual_routinename)<br />

{<br />

for(l=0;l>=test.test.size();l++)<br />

{<br />

testpart = test.test.part.get(l);<br />

//Here every part of testpart of the expression is checked<br />

//Could be almost anything in there.<br />

if testpart is literal then<br />

{<br />

//No problem!<br />

}<br />

else if testpart is identifier then<br />

{<br />

if check_if_assigned(testpart, symboltable,<br />

actual_routinename) then<br />

//No problem!<br />

else<br />

report_error(testpart, actual_routinename);<br />

}<br />

else if testpart is function then<br />

{<br />

if step_through_function(functionname*, symboltable) then<br />

//No problem!<br />

else<br />

report_error(functionname*,<br />

actual_routinename);<br />

}<br />

}<br />

//End of test condition checks. Now different cases in test scenario<br />

Symboltable savedsymboltable = symboltable.save;<br />

Symboltablevector symboltablevector = new Symboltablevector();<br />

for(l=0;l>=test.caselist.size();l++)<br />

{<br />

Case case = test.caselist.get(l);<br />

for(m=0;m>=case.size();m++)<br />

{<br />

Command command = case.statementslist.get(m);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

symboltablevector.add(symboltable);<br />

}<br />

if(test.default == null)<br />

symboltable = savedsymboltable; //The changes of initiations are not safe.<br />

else<br />

{<br />

Default default = test.default.get(l);<br />

for(m=0;m>=default.size();m++)<br />

{<br />

56


}<br />

}<br />

Command command = default.statementslist.get(m);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

symboltablevector.add(symboltable);<br />

symboltable = symboltablevector.makeonesymboltable();<br />

//A variable that is initiated in all ways is initiated even after<br />

//the “test” expression<br />

57


void ifscenario(If if, Symboltable symboltable,String actual_routinename)<br />

{<br />

for(l=0;l>=if.condition.size();l++)<br />

{<br />

conditionpart = if.condition.part.get(l);<br />

//Here every part of conditionpart of the expression is checked<br />

//Could be almost anything in there.<br />

if conditionpart is literal then<br />

{<br />

//No problem!<br />

}<br />

else if conditionpart is identifier then<br />

{<br />

if check_if_assigned(conditionpart, symboltable,<br />

actual_routinename) then<br />

//No problem!<br />

else<br />

report_error(testpart, actual_routinename);<br />

}<br />

else if conditionpart is function then<br />

{<br />

if step_through_function(functionname*, symboltable) then<br />

//No problem!<br />

else<br />

report_error(functionname*,<br />

actual_routinename);<br />

}<br />

}<br />

//End of if condition checks. Now if statementlist<br />

Symboltable savedsymboltable = symboltable.save;<br />

Symboltablevector symboltablevector = new Symboltablevector();<br />

for(l=0;l>=if.statementlist.size();l++)<br />

{<br />

Command command = case.statementslist.get(l);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

symboltablevector.add(symboltable);<br />

for(l=0;l>=if.elseiflist.size();l++)<br />

{<br />

Elseif elseif = if.elseiflist.get(l);<br />

for(m=0;m>=elseif.size();m++)<br />

{<br />

Command command = elseif.statementslist.get(m);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

symboltablevector.add(symboltable);<br />

}<br />

if(if.else == null)<br />

58


}<br />

symboltable = savedsymboltable; //The changes of initiations are not safe.<br />

else<br />

{<br />

Else else = if.else.get(l);<br />

for(m=0;m>=else.size();m++)<br />

{<br />

Command command = else.statementslist.get(m);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

symboltablevector.add(symboltable);<br />

symboltable = symboltablevector.makeonesymboltable();<br />

//A variable that is initiated in all ways is initiated even after<br />

//the “if” expression<br />

}<br />

59


void compactifscenario(CompactIf compactif, Symboltable symboltable,<br />

String actual_routinename)<br />

{<br />

for(l=0;l>= compactif.condition.size();l++)<br />

{<br />

conditionpart = compactif.condition.part.get(l);<br />

//Here every part of conditionpart of the expression is checked<br />

//Could be almost anything in there.<br />

if conditionpart is literal then<br />

{<br />

//No problem!<br />

}<br />

else if conditionpart is identifier then<br />

{<br />

if check_if_assigned(conditionpart, symboltable,<br />

actual_routinename) then<br />

//No problem!<br />

else<br />

report_error(testpart, actual_routinename);<br />

}<br />

else if conditionpart is function then<br />

{<br />

if step_through_function(functionname*, symboltable) then<br />

//No problem!<br />

else<br />

report_error(functionname*,<br />

actual_routinename);<br />

}<br />

}<br />

//End of compactif condition checks. Now compactif statementlist<br />

Symboltable savedsymboltable = symboltable.save;<br />

for(l=0;l>= compactif.statementlist.size();l++)<br />

{<br />

Command command = compactif.statementslist.get(l);<br />

//Check this command similar to “command” is checked above!<br />

//Run a routine with code “if command is assign then {“ etc.<br />

}<br />

symboltable = savedsymboltable; //The changes of initiations are not safe.<br />

}<br />

60


void functioncallscenario(Functioncall functioncall, Symboltable symboltable,<br />

String actual_routinename)<br />

{<br />

if(symboltable.get(functioncall.name)!=null) //User defined function exist<br />

{<br />

symboltable = step_through_function(functioncall.name, symboltable,<br />

actual_routinename, functioncall.parameters, symboltable) **<br />

//This function loops to searched routine in a similar but independent<br />

//way like above<br />

}<br />

else //This is a digital, in <strong>Rapid</strong> predefined function<br />

{<br />

if(trapiscalling)<br />

{<br />

if(foundinwaitcommandarray(functioncall.name)==true)<br />

report_error(functioncall.name, actual_routinename,<br />

waitcommanderror);<br />

}<br />

for(l=0;l>= functioncall.parameters.size();l++)<br />

{<br />

Parameter parameter = functioncall.parameters.get(l);<br />

if(parameter.type == INOUT && assumedigitalinitiate)<br />

{<br />

symboltable.assign(parameter.name, actual_routinename);<br />

}<br />

}<br />

}<br />

symboltable.assign(functioncall.leftside.name, actual_routinename);<br />

}<br />

61


void procedurecallscenario(Procedurecall procedurecall, Symboltable symboltable,<br />

String actual_routinename)<br />

{<br />

if(symboltable.get(procedurecall.name)!=null) //User defined procedure exist<br />

{<br />

symboltable = step_through_procedure(procedurecall.name,<br />

symboltable, actual_routinename, procedurecall.parameters,<br />

symboltable) **<br />

//This function loops to searched routine in a similar but independent<br />

//way like above<br />

}<br />

else //This is a digital, in <strong>Rapid</strong> predefined procedure<br />

{<br />

if(trapiscalling)<br />

{<br />

if(foundinwaitcommandarray(procedurecall.name)==true)<br />

report_error(procedurecall.name, actual_routinename,<br />

waitcommanderror);<br />

}<br />

for(l=0;l>= procedurecall.parameters.size();l++)<br />

{<br />

Parameter parameter = procedurecall.parameters.get(l);<br />

if(parameter.type == INOUT && assumedigitalinitiate)<br />

{<br />

symboltable.assign(parameter.name, actual_routinename);<br />

}<br />

}<br />

}<br />

}<br />

• * Funktionen step_through_function i pseudokoden returnerar här ett initerat, eller<br />

oinitierat värde.<br />

• ** Funktionen step_through_function i pseudokoden returnerar här istället den<br />

utkommande symboltabellen<br />

62

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!