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
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