Asynkrony har blivit en naturlig del av JavaScript, som ett försök att förbättra skalbarheten och prestandan hos moderna webbapplikationer. I detta syfte ger JavaScript programmerare ett brett utbud av konstruktioner och funktioner för att utveckla kod som utför asynkrona beräkningar, inklusive men inte begränsat till timers, löften och icke-blockerande I/O. Men dataflödet som införs av asynkrony är implicit, och inte alltid välförstådd av de utvecklare som introducerar många asynkrony-relaterade fel till sina program. Vad värre är, det finns få verktyg och tekniker tillgängliga för analys och resonemang om sådana asynkrona applikationer. I detta arbete tar vi itu med denna fråga genom att utforma och genomföra ett av de första statiska analyssystem som kan hantera nästan alla asynkrona primitiver av JavaScript fram till den 7: e upplagan av ECMAScript specifikation. Specifikt, vi introducerar callback grafen, en representation för att fånga dataflödet mellan asynkron kod. Vi utnyttjar callback grafen för att utforma en mer exakt analys som respekterar exekveringsordningen mellan olika asynkrona funktioner. Vi parameteriserar vår analys med en ny kontext-känslighet smak, och vi hamnar med flera analysvariationer för att bygga callback graf. Vi utförde ett antal experiment på en uppsättning handskrivna och verkliga JavaScript-program. Våra resultat visar att vår analys kan tillämpas på medelstora program med i genomsnitt 79% precision. Resultaten tyder vidare på att analyskänsligheten är fördelaktig för den stora majoriteten av riktmärkena. Specifikt kan den förbättra precisionen med upp till 28,5%, samtidigt som den uppnår en 88% precision i genomsnitt utan att kraftigt offra prestanda. Nyckelord och fraser statisk analys, asynkrony, JavaScript JavaScript är en integrerad del av webbutveckling. Sedan den första utgåvan 1995 har den utvecklats från en enkel skriptspråk-främst används för att interagera med webbsidor-till ett komplext och allmänt syfte programmeringsspråk som används för att utveckla både klient- och server-side-applikationer. Uppkomsten av Web 2.0 tillsammans med de dynamiska funktionerna i JavaScript, som underlättar en flexibel och snabb utveckling, har lett till en dramatisk ökning av dess popularitet. Enligt den årliga statistiken från Github, som är den ledande plattformen för att vara värd för programvara med öppen källkod, är JavaScript det överlägset mest populära och aktiva programmeringsspråket från 2014 till 2018 [13]. Även om JavaScripts dominans är imponerande, har samhället i stor utsträckning kritiserat det eftersom det ger upphov till många farhågor om programmens säkerhet eller korrekthet [35]. JavaScript är ett språk med en hel del dynamiska och metaprogrammering funktioner, inklusive men arXiv:1901.0357v1 [cs.PL] 11 Jan 2019 XX:2 Statisk analys för Asynkrona JavaScript-program som inte är begränsade till prototypbaserade arv, dynamiska fastighetslookups, implicita typ tvång, dynamisk kodbelastning, etc. Många utvecklare ofta inte förstår eller inte korrekt använder dessa funktioner, införa fel i sina program-som är svåra att felsöka-eller baleful säkerhetssårbarheter. I detta sammanhang har JavaScript lockat många ingenjörer och forskare under det senaste årtiondet att 1) studera och resonera om dess säregna egenskaper, och 2) utveckla nya verktyg och tekniker-såsom typanalysatorer [18, 22, 20], IDE och refaktoring verktyg [5, 6, 7, 10], eller bugg och sårbarhetsdetektorer [28, 14, 33, 30, 4, 36] -för att hjälpa utvecklare med utveckling och underhåll av sina applikationer. Programanalys, och särskilt statisk analys, som automatiskt beräknar fakta om programmets beteende utan att faktiskt köra den, spelar en avgörande roll i utformningen av sådana verktyg [37]. Dessutom har bevarandet av skalbarheten hos moderna webbapplikationer blivit mer kritisk än någonsin. Som ett försök att förbättra genomströmningen av webbprogram har JavaScript börjat anta ett händelsedrivet programmeringsparadigm [3]. I detta sammanhang utförs en kod asynkront som svar på vissa händelser, t.ex. användarinmatning, ett svar från en server, data som läses från disk etc. Under de första åren av JavaScript, någon kunde stöta på att asynkrony främst i en webbläsarmiljö t.ex., DOM händelser, AJAX samtal, timers, etc. Under de senaste åren har dock asynkronin blivit en framträdande och inneboende del av språket, eftersom nyare versioner av språkets kärnspecifikation (dvs. ECMAScript) har infört allt fler asynkrony-relaterade funktioner. Till exempel, ECMAScript 6 introducerar löften; en viktig del av asynkron programmering som gör det möjligt för utvecklare att spåra tillståndet för en asynkron beräkning enkelt. Specifikt, tillståndet för ett löfte objekt kan vara ett av: uppfyllt: den associerade operationen är komplett, och löftet objekt spårar dess resulterande värde. avvisade: den tillhörande åtgärden misslyckades, och det utlovade objektet spårar dess felaktiga värde. väntande: den tillhörande operationen har varken slutförts eller misslyckats. Löften är särskilt användbara för asynkron programmering eftersom de ger ett intuitivt sätt att skapa kedjor av asynkron beräkning, underlätta verkställighet av verkställighetsorder samt felutbredning [11, 10]. För att göra detta utlöser löften verkställandet av vissa funktioner (dvs. återuppringningar) beroende på deras tillstånd, t.ex. återkallningar som utförs när ett löfte har uppfyllts eller förkastats. Av den anledningen ger API av löften metoden x.then (f1, f2) för att registrera nya callbacks (dvs, f1 och f2) på ett löfte objekt x. Till exempel, vi kallar callback f1 när löftet är uppfyllt, medan vi trigga callback f2 när löftet förkastas. Metoden x.then () returnerar ett nytt löfte som returvärdet för de medföljande callbacks (dvs. f1, f2) uppfyller. Sedan den första introduktionen till språket har JavaScript-utvecklare brett anammat löften; en studie 2015 visade att 75% av JavaScript-ramverk använder löften [11]. Med utgångspunkt i löftena har nyare versioner av ECMAScript lagt till nya språkfunktioner relaterade till asynkroni. I ECMAScript 8 har vi de async/avänta nyckelorden. Async-nyckelordet förklarar en funktion som asynkron som returnerar ett löfte som uppfylls med dess returvärde, medan x väntar på verkställandet av den asynkrona funktion i vilken placeras, tills löftet objekt x regleras (dvs., det är antingen uppfyllt eller förkastas). Den senaste utgåvan av ECMAScript (ECMAScript 9) lägger till asynkrona iteratorer och generatorer som tillåter utvecklare att iterera över asynkrona datakällor. Utöver löften skrivs många JavaScript-program för att utföra icke-blockerande I/O-operationer. Till skillnad från traditionella uttalanden, när vi utför en icke-blockerande I/O-operation, avbryts inte utförandet förrän den operationen avslutas. Till exempel görs en filsystemoperation asynkront, vilket innebär att utförandet fortsätter till nästa uppgifter medan I/O sker. Programmerare blandar ofta asynkront I/O med löften. Tänk till exempel på det verkliga exemplet i figur 1. Vid rad 1 utför koden en asynkron begäran och returnerar ett löftesobjekt som uppfylls asynkront när begäran lyckas. Därefter kan det utlovade objektet användas för att behandla svaret från servern asynkront. På raderna 2-23 skapar vi till exempel en lovande kedja. Den första callback av denna kedja (raderna 2-14) klonar svaret på begäran, och tilldelar den egenskapssvaret av objektet honoka (linje 3). Sedan tolkar den svarets kropp enligt dess typ och uppfyller det löftesobjekt som tilldelats av den första anropande då(). Den andra callback (raderna 15-23) hämtar rubrikerna i svaret-som uttalandet på rad 3 tilldelar till honoka.respons-och om innehållstypen är "application/json", konverterar den data från svaret till ett JSON-objekt (raderna 17-19). Liksom andra egenskaper hos JavaScript, programmerare förstår inte alltid tydligt asynkrony, som ett stort antal asynkrony-relaterade frågor som utfärdas på populära platser som stackoverflow.com 1 [26, 25], eller antalet fel som rapporteras i öppen källkod arkiv [38, 4] indikerar. Befintliga verktyg och tekniker har dock begränsat (och i många fall inget) stöd för asynkrona program. I synnerhet, befintliga verktyg fokuserar främst på händelsesystemet för klientsidan JavaScript-applikationer [17, 32], och de saknar stöd för de nyare funktioner som läggs till språket som löften. Dessutom, många tidigare verk konservativt anses att alla asynkrona callbacks som behandlas av händelsen loop-programmet punkt som ständigt väntar på nya händelser att komma och är ansvarig för schemaläggning och utförande av callbacks-kan kallas i någon ordning [17, 32, 20]. Ett sådant tillvägagångssätt kan emellertid leda till oförutsebara och falska positiva resultat. Tillbaka till exemplet i figur 1, Det är lätt att se att en analys, som inte respekterar exekveringsordern mellan den första och den 1 https://stackoverflow.com/ andra callback, kommer att rapportera ett typfel på rad 17 (tillgång till honoka.response.headers.get (" Innehåll - Type ")). Specifikt, en otydlig analys antar att den callback definieras på raderna 15-23 kan utföras först; därför, honoka.respons, tilldelas på rad 3, kan vara uninitialized. I detta arbete tar vi itu med dessa frågor, genom att utforma och genomföra en statisk analys som behandlar asynkrona JavaScript-program. För det ändamålet definierar vi först en modell för att förstå och uttrycka ett brett spektrum av JavaScripts asynkrona konstruktioner, och sedan utformar vi en statisk analys baserad på detta. Vi föreslår en ny representation, som vi kallar callback graf, som ger information om exekveringsordningen för asynkron kod. Den callback graf som föreslås i detta arbete försöker sprida ljus över hur dataflödet mellan asynkron kod förökas. I motsats till tidigare verk utnyttjar vi callback-grafen och utformar en mer exakt analys som respekterar exekveringsordningen för asynkrona funktioner. Dessutom parameteriserar vi vår analys med en ny kontext-känslighetsstrategi utformad för asynkron kod. Specifikt skiljer vi dataflödet mellan asynkrona callbacks baserat på det löftesobjekt som de har registrerat sig på eller nästa beräkning till vilken utförandet fortskrider. Vi föreslår en beräkning, d.v.s. λ q, för att modellera asynkrona funktioner i JavaScript-språket, inklusive timers, löften och asynkrona I/O-operationer. Vår kalkyl är en variation av befintlig kalkyl [24, 25], och ger konstruktioner och domäner som är särskilt riktade för vår analys ( §2). Vi designar och implementerar en statisk analys som kan hantera asynkrona JavaScript-program genom att utnyttja den abstrakta versionen av λ q. Såvitt vi vet är vår analys den första att hantera JavaScript-löften (§3.1). Vi föreslår den callback grafen, en representation som illustrerar exekveringsordningen mellan asynkrona funktioner. Med utgångspunkt i detta föreslår vi en mer exakt analys (dvs. callback-känslig analys) som internt konsulterar callback-grafen för att hämta information om asynkrona funktioners tidsmässiga samband så att den sprider dataflödet i enlighet med detta. Dessutom parameteriserar vi vår analys med en ny kontextkänslighetssmak (dvs QR-känslighet) som används för att särskilja asynkrona callbacks. ( §3.2, §3.3). Vi utvärderar prestandan och precisionen i vår analys på en uppsättning mikroriktmärken och en uppsättning verkliga JavaScript-moduler. För den otålige läsaren finner vi att vår prototyp kan analysera medelstora asynkrona program, och analyskänsligheten är till nytta för att förbättra analysprecisionen. Resultaten visade att vår analys i genomsnitt kan uppnå en noggrannhet på 79% för callback-grafen. Analyskänsligheten (dvs. callback- och QR-känslighet) kan ytterligare förbättra precisionen i callback-grafen med upp till 28,5 % och minska det totala antalet typfel med 16,7 % som observerats i verkliga riktmärken (§4). Som utgångspunkt måste vi definiera en modell för att uttrycka asynkroni. Målet med denna modell är att ge oss grunden för att få en bättre förståelse för asynkrona primitiver och underlätta utformningen av en statisk analys för asynkrona JavaScript-program. Denna modell uttrycks genom en kalkyl kallad λ q ; en förlängning av λ js som är kärnan kalkyl för JavaScript utvecklat av [15]. Den λ q calculus är utformad för att vara flexibel så att den kan modellera olika källor av asynkrony som finns i språket fram till den 7: e upplagan Nyckelkomponenten i vår modell är kö objekt. Köobjekt är nära relaterade till JavaScript-löften. Specifikt, en kö objekt-liknande ett löfte-spår tillståndet för ett asynkront jobb, och det kan vara i ett av följande tillstånd: 1) väntande, 2) uppfyllt eller 3) förkastas. En kö objekt kan utlösa utförandet av callbacks beroende på dess tillstånd. Inledningsvis väntar ett köobjekt. Ett väntande köobjekt kan övergå till ett uppfyllt eller ett avvisat köobjekt. Ett köobjekt kan uppfyllas eller förkastas med ett angivet värde som senare skickas som argument vid utförandet av dess callbacks. När ett köobjekt är antingen uppfyllt eller avvisat är dess tillstånd slutgiltigt och kan inte ändras. Vi behåller samma terminologi som löften, så om ett köobjekt antingen uppfylls eller avvisas, kallar vi det bestämt. Figur 2 visar syntaxen för λ q. För korthet presenterar vi endast de nya konstruktioner som lagts till språket. Specifikt lägger vi till åtta nya uttryck: Detta uttryck skapar en färsk väntande kö objekt utan callbacks i samband med det. e 1.fullfill(e 2 ): Detta uttryck uppfyller mottagaren (dvs, uttrycket e 1 ) med värdet e 2. e 1. reject(e 2 ): Detta uttryck avvisar mottagaren (dvs, uttrycket e 1 ) med värdet e 2. e 1.registerFul(e 2, e 3,. . : Detta uttryck registrerar uppringningen e 2 till mottagaren. Denna uppringning utförs endast när mottagaren är uppfylld. Detta uttryck förväntar sig också ett annat kö objekt passerade som det andra argumentet, dvs. e 3. Detta kö objekt kommer att uppfyllas med returvärdet för callback e 2. Detta gör att vi kan modellera kedjor av löften där ett löfte löser med returvärdet av ett annat löftes callback. Statisk analys för Asynkrona JavaScript-program Detta uttryck kan också få valfria parametrar (dvs. uttryckt genom ". .. ") med vilken e 2 kallas om kö objektet är uppfyllt med  på värdet. Vi kommer senare att rättfärdiga intuitionen bakom detta. e 1.registerRej(e 2, e 3,........................................................... . : Samma som e.registerFul(. . ) men denna gång den givna uppringning utförs när mottagaren avvisas. tillägg (e): Det här uttrycket lägger till köobjektet e till toppen av den nuvarande kökedjan. Som vi kommer att se senare motsvarar det översta elementet i en kökedja det köobjekt som behövs för att avvisas om avrättningen stöter på ett okänt undantag. Observera att vi använder utvärderingssammanhang [8, 15, 26, 24, 25] för att uttrycka hur utvärderingen av ett uttryck fortskrider. Symbolen E betecknar vilken underuttryck som för närvarande utvärderas. Till exempel, E.fulfill(e) beskriver att vi utvärderar mottagaren av uppfylla, medan v.fulfill(E) innebär att mottagaren har utvärderats till ett värde v, och utvärderingen nu ligger på argumentet att uppfylla. Utöver dessa uttryck introducerar λ q calculus ett nytt värde, d.v.s. Detta värde skiljer sig från null och odefinierat eftersom det uttrycker frånvaron av värde och det inte motsvarar något JavaScript-värde. Figur 3 presenterar de domäner som införts i semantik av λ q. I synnerhet är en kö en partiell karta över adresser för att köa objekt. Symbolen l i -där i är ett positivt heltal anger en adress. Observera att uppsättningen av adresser också innehåller två särskilda reserverade adresser, dvs, l tid, l io. Vi använder dessa två adresser för att lagra de köobjekt som ansvarar för att hålla tillståndet för callbacks relaterade till timers respektive asynkron I/O (avsnitt 2.2 förklarar hur vi modellerar dessa JavaScript-funktioner). Ett köobjekt beskrivs av dess tillståndsutrop att ett köobjekt antingen är väntande eller uppfyllt och förkastas med en värde-en sekvens av callbacks utförs vid uppfyllelse, och en sekvens av callbacks kallas på avvisande. Det sista elementet i ett köobjekt är en adress som motsvarar ett annat köobjekt som är beroende av strömmen, dvs. det regleras när det aktuella köobjektet regleras, och med samma tillstånd. Vi skapar sådana beroenden när vi gör upp ett köobjekt med ett annat köobjekt. I detta fall är mottagaren beroende av det köobjekt som används som argument. När vi går över till områdena för callbacks ser vi att en callback består av en adress, en funktion och en lista över värden (dvs. argument för funktionen). Observera att den första komponenten anger adressen till det köobjekt som funktionens returvärde kommer att uppfylla. I listan över callbacks är vi schemalagda callbacks, vi behåller den ordning i vilken callbacks är schemalagda. Observera att vi behåller ytterligare en lista över callbacks (dvs., τ  på schemalagd T imerIO) där vi lagrar callbacks registrerade på kö objekt som ligger på adresserna l tid, l io. Vi skjuter upp diskussionen om varför vi har två separata listor fram till punkt 2.1.3. En kökedja är en sekvens av adresser. I en kökedja lagrar vi det köobjekt som vi avvisar, om det finns ett okänt undantag i den aktuella avrättningen. Särskilt när vi stöter på ett okänt undantag, inspekterar vi det översta elementet i kökedjan, och vi förkastar det. Om kökedjan är tom, sprider vi undantaget till samtalstacken som vanligt. Utrustade med lämpliga definitioner av syntax och domäner, i figur 4, Vi presenterar de små steg semantik av λ q som är en anpassning av [24, 25]. Observera att vi visar de mest representativa reglerna för våra semantik; vi utelämnar några regler för korthet. För vad som följer, den binära transaktionen som betecknas med symbolen · innebär att ett element läggs till i en lista, den operation som anges av :: står för listan konkatenering, medan ↓ i avser projektionen av i th elementet. Reglerna för våra semantik anta följande form: Detta formulär uttrycker att en given kö π, en kö kedja ∂, två sekvenser av callbacks σ och τ, och ett uttryck e i utvärderingssammanhang E leder till en ny kö π, en ny kö kedja ∂, två nya sekvenser av callbacks σ och τ, och ett nytt uttryck e i samma utvärderingssammanhang E, förutsatt att uttrycket e reduceras till e (dvs. e → e ). Regeln [e-kontext] beskriver detta beteende. Regeln [newQ] skapar ett nytt köobjekt och lägger till det i kön med en ny adress. Detta nya kö objekt är väntande, och det har inga callbacks relaterade till det. Den [fullbordande-pending] regeln visar fallet när vi uppfyller en väntande kö objekt med värdet v, där v =  på och inte motsvarar något kö objekt. Framför allt ändrar vi först mottagarobjektets tillstånd från att "använda" till "uppfylla". I sin tur uppdaterar vi de redan registrerade callbacks (om några) genom att ställa in värdet v som det enda argumentet för dem. Sedan lägger vi till de uppdaterade callbacks till listan över schemalagda callbacks (förutsatt att mottagaren varken är l tid eller l io ). Observera också att det ursprungliga uttrycket reduceras till l.fulfill (v), det vill säga, om det finns ett beroende kö objekt l, Vi uppfyller också att kö objekt med samma värde v. Den [fullfill-pend-pend-pend] beskriver scenariot att uppfylla en väntande kö objekt p med en annan väntande kö objekt v. I detta fall uppdaterar vi kön π genom att göra kön objekt p att vara beroende av v. Detta innebär att vi avgöra p när vi bosätta v och på samma sätt. Observera att både p och v fortfarande väntar. Den [fullständig-pend-full] regel visar fallet när vi försöker uppfylla en väntande kö objekt p med den uppfyllda kö objekt v. Sedan p löser med värdet som v uppfylls. Detta uttrycks genom det resulterande uttrycket p.fulfill (v ). Regeln om [fullföljande-pending- till] fångar upp fallet när vi uppfyller ett köobjekt med ett värde. Denna regel är densamma med [fullfölja-pending] regeln, men den här gången uppdaterar vi inte argumenten för några callbacks registrerade på kön objektet p. Den [fullfölja-avslutade] regeln illustrerar fallet när vi försöker uppfylla en etablerad kö objekt. Lägg märke till att denna regel varken påverkar kön π eller listorna över schemalagda callbacks σ och τ. Den [registerFul-spending] regeln lägger den medföljande callback f till listan över callbacks som vi bör köra när kö objektet p är uppfyllt. Observera att denna regel också associerar, ∂, τ, E[e] → π,  och τ, τ, E[e ] [e-kontext] ί, σ, τ, E[α] [newQ] ί, τ, E[l.fulfill(v)] [fulfill-pending] ∂, σ, τ, E[undef] [fulfill-pend-pend]  och σ, τ, E[p.registerFul(f, p, r, n1, n2,. .., nn) ∂, σ, τ, E[p.registerFul(f, p, r, n1, n2,........................................................................................................... .., nn)  och τ, τ, E[undef] [registerFul-fullbordad]  på, ί, τ, E[p.registerFul(f, p, r, n1, n2,......., n) .., nn)  och τ, τ, E[undef] [registerFul-fullfylld-.]  och ί, τ, E[p.registerFul(f, p, r, n1, n2,......., n) .., nn), σ, τ, E[undef] [bilaga]  och ί, τ, E[undef] [pop], σ, τ, E[p.reject(v]] [terror] figur 4 Semantikerna i λq XX:9 figur 5 Semantiken i händelseloopen. denna callback med kön objekt p ges som det andra argumentet. Detta kö objekt p uppfylls vid avslutandet av f. Dessutom lägger denna regel till eventuella extra argument som passerats i registerFul som argument för f. Den [ registerFul-fullsatt] regeln lägger till den givna callback f till listan σ (förutsatt att mottagaren är varken l tid eller l io ). Vi använder mottagarens uppnådda värde som det enda argumentet för den givna funktionen. Liksom den föregående regeln, det relaterar den medföljande kö objekt p med verkställandet av callback. Den här gången ignorerar vi eventuella extra argument som passerats i registerFul, eftersom vi uppfyller kö objekt p med ett värde som inte är  på. Regeln [registerFul-fullsatt- och] beskriver det fall där vi registrerar en callback f på ett köobjekt som uppfylls med ett värde. Till skillnad från [registerFul-uppfylld] regel, denna regel försummar inte några extra argument som antagits i registerFul. I synnerhet anger kommissionen dessa argument som parametrar för den givna återkallelsen. Denna distinktion gör det möjligt för oss att explicit vidarebefordra argument till en återuppringning. Framför allt är dessa argument inte beroende av det värde med vilket ett köobjekt uppfylls eller avvisas. Regeln [registerFul-timer-io- och] är densamma som den föregående, men den här gången hanterar vi köobjekt som ligger antingen vid l tid eller vid l io. Således lägger vi till den givna callback f till listan τ istället för σ. Regeln [tillägg] lägger till elementet p längst fram i den nuvarande kökedjan. Observera att denna regel kräver att elementet p är ett köobjekt (dvs. p  på dom(π)). Å andra sidan avlägsnar [pop] regeln det översta elementet i kökedjan. Den [terroriska] regeln visar fallet när vi stöter på ett okänt undantag, och kökedjan är inte tom. I så fall sprider vi inte undantaget till anroparen, men vi öppnar kökedjan och får det översta elementet. I sin tur avvisar vi kön objekt p specificerat i det översta elementet. På så sätt fångar vi det faktiska beteendet hos de otyglade undantag som utlösts under verkställandet av en asynkron återkallning. En läsare kanske undrar varför vi håller två separata listor, dvs. listan τ för att hålla callbacks kommer från l tid eller l io kö objekt, och listan σ för callbacks av alla andra kö objekt. Intuitionen bakom detta designval är att det är bekvämt för oss att modellera händelseloopens konkreta semantik korrekt. Framför allt tilldelar genomförandet av eventloopen olika prioriteringar till återkallningarna beroende på deras art [24, 31]. Händelseloopen behandlar till exempel en callback av ett löftesobjekt innan någon callback av en timer eller en asynkron I/O-operation oberoende av deras registreringsorder. I detta sammanhang visar figur 5 händelseloopens semantik. Regeln [event-loop] dyker upp den första schemalagda återkopplingen från listan σ. Sedan får vi det kö objekt som anges i den callback, och vi fäster den på framsidan av kökedjan. Genom att lägga till köobjektet q längst upp i kökedjan kan vi avvisa köobjektet om det finns en obruten figur 6 Utöka syntaxen för λq för att hantera timers och asynkront I/O. undantag under verkställandet av f. I detta fall, utvärderingen av uppfylla kommer inte att ha någon effekt på den redan avvisade kö objekt q (återkalla [fullsatt-avgjord] regel). Observera också hur händelseloopen reduceras, d.v.s. q.fulfill(f (a)); pop(); •. Specifikt, när vi kör callback f och uppfyller beroende kö objekt q med returvärdet för den callback, utvärderar vi pop(), dvs vi pop den övre delen av kökedjan innan vi omvärderar händelsen loop. Detta är en invariant av semantiken i händelseloopen: varje gång vi utvärderar den är kökedjan alltid tom. Regeln [event-loop-timers-io] hanterar fallet när listan är tom. Med andra ord, denna regel anger att om det inte finns några callbacks, som varken kommer från l tid eller l io kö objekt, inspektera listan τ, och plocka icke-deterministiskt en av dessa. Genom att välja en callback icke-deterministiskt kan vi överskatta det faktiska beteendet hos händelseloopen när det gäller dess olika genomförandefaser [24]. Sammantaget beskriver denna regel den schemaläggning politik som presenteras i arbetet [24], där vi inledningsvis letar efter eventuella callbacks av löften, och om det finns, väljer vi en av dessa. Annars väljer vi alla callback i samband med timers och asynkron I/O slumpmässigt. (f, q, n1, q.registerFul)(f, q, n1, q, q, q, q, q, q, q, q, q, q, n1, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, n1, q, q, q, q, q, q, q, q, q, q, q, q, n1, q, q, q, q, q, q, q, n1, q, q, q, q, q, q, q, n1, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, n1, q, q, q, q, q, q, q, q, q, q, q, q, q, q, n1, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q .. ) [add-timer-callback]  och ί, τ, addioCallback(f, n1,...... )...... )........................... . ) → π, ∂, σ, q.registerFul(f, q, n1,. . ) [add-io-callback] Till modell timers och asynkron I/O följer vi ett liknande tillvägagångssätt till arbetet av [24]. Specifikt, vi börjar med en inledande kö π, som innehåller två kö objekt: q tid, och q io som ligger vid l tid och l io respektive. Både q-tid och q io initieras som (uppfylls,...), [], [],...). Dessutom utökar vi syntaxen för λ q genom att lägga till ytterligare två uttryck. Figur 6 visar den utvidgade syntaxen för λ q för att hantera timers och asynkron I/O, medan figur 7 visar reglerna för dessa uttryck. De nya uttrycken har hög korrespondens med varandra. Specifikt, addTimerCallback (. .. ) konstruktion lägger till callback e 1 till kö objektet ligger på adressen l tid. Vi kallar den medföljande callback Argumenten för denna callback är alla valfria parametrar som passerar i addTimerCallback, dvs., e 2, e 3, och så vidare. Från Figur 7, Vi observerar att [add-timer-callback] regel hämtar kö objekt q motsvarar adressen l tid. Kom ihåg igen att l-tiden kan hittas i den ursprungliga kön. Därefter reduceras det givna uttrycket till q.registerFul(f, q, n 1,. ... ).......................................................................................................... I synnerhet lägger vi till en ny callback f till XX:11 kö objekt hittades vid l tid. Observera att vi passerar samma kö objekt (dvs. q) som det andra argumentet i registerFul. Det innebär att verkställandet av f inte påverkar något kö objekt eftersom q redan är avvecklad. Kom ihåg att enligt [fullbordade-avslutade] regeln (Figur 4 ), försöker uppfylla (och på liknande sätt avvisa) ett stadgat kö objekt inte har någon effekt. Utöver detta, eftersom q uppfylls med  på, de extra argumenten (dvs., n 1,. . ) också antas som argument i åberopandet av f. Semantik av addioCallback (. .. ) primitiv är samma sak som för addTimerCallback (. . ); Men denna gång använder vi kö objektet ligger på l io. De köobjekt och deras verksamhet som introduceras i λ q är mycket nära relaterade till JavaScript löften. Därför är det enkelt att översätta löftenas verksamhet till λ q. Vi modellerar varje egenskap och metod (utom för Promise.all()) genom att troget följa ECMAScript specifikation. Figur 8, Vi ser hur vi modellerar Promise.resolve () funktion i termer av λ q. Funktionen JavaScript Promise.resolve () skapar ett nytt löfte och löser det med det givna värdet. Enligt ECMAScript, om det givna värdet är en bärbar, (dvs. ett objekt som har en egenskap som heter "då" och den egenskapen är en anropbar), det skapade löftet löser asynkront. Specifikt, vi utför funktionsvärdet. sedan () asynkront, och vi passerar lösa funktioner (dvs., uppfylla, avvisa) som dess argument. Observera hur uttryckligheten hos λ q kan modellera denna källa till asynkroni (raderna 4-6). Först skapar vi ett nytt kö objekt t, och vi uppfyller det med  på raderna 4, 5. Sedan, på rad 6, schemalägger vi utförandet av value.then () genom att registrera det på den nyskapade kö objektet t. Observera att vi också passera löfte.fullborda och lova.reject som extra argument. Det innebär att dessa funktioner kommer att vara de faktiska argumenten för värde.then () eftersom t uppfylls med  på. Å andra sidan, om värdet inte är en bärbar, vi synkront lösa det skapade löftet med hjälp av löftet.uppfylla konstruktion på linje 8. Kärnanalys Den λ q-kalkyl som presenteras i Avsnitt 2 är pekstenen i den statiska analys som föreslås för asynkrona JavaScript-program. Analysen är utformad för att vara ljud; alltså, vi utforma abstrakta domäner och semantik som över approximerar beteendet hos λ q. För närvarande finns det få implementationer tillgängliga för asynkron JavaScript, och tidigare insatser fokuserar främst på att modellera händelsesystemet för klientsidan applikationer [17, 32]. Såvitt vi vet är det den första statiska analysen av ES6-löften. I resten av detta avsnitt beskrivs detaljerna i analysen. kö = Addr → P( QuueObject) queueObject = QuueState × P (Callback) × P(Callback) × P(Adder) qs  och QuueState = {consumping} ({fullsatt, avvisad} × V alue) clb.callback = Addr × F × V alue * ί ScheduledCallbacks = (P(Callback)) * τ (P(CallerIO) = (P(CallerIO)) * (P(Callback) * (P(Caller) *) * De abstrakta domänerna i λq. Figur 9 presenterar de abstrakta domänerna av λ q calculus som ligger till grund för vår statiska analys. Nedan gör vi en sammanfattning av våra primära designval. Abstract Addresses: Som utgångspunkt använder vi distribution site abstraction för att modellera utrymmet för adresser. Det är det standardsätt som används i litteraturen för abstrakta adresser som håller domänen ändlig [18, 26]. Observera också att vi fortfarande definierar två interna adresser, dvs l tid, l io, som motsvarar adresserna för de kö objekt som ansvarar för timers och asynkron I/O respektive. Vi definierar en abstrakt kö som den partiella kartan över abstrakta adresser till en del av kraftmängden abstrakta köobjekt. Därför kan en adress peka på flera kö objekt. Denna abstraktion över approximerar beteendet hos λ q och tillåter oss att fånga alla möjliga program beteenden som kan härröra från analysen imprecision.
Sotiropoulos m.fl. REF introducerar λ q, en modell av asynkrony i JavaScript, samt callback grafen, som beskriver möjliga beställningar av callback körning.
Static Analysis for Asynchronous JavaScript Programs
{'venue': 'ArXiv', 'journal': 'ArXiv', 'mag_field_of_study': ['Computer Science']}