Toekomstbestendige JavaScript Debugger-implementatie van Firefox

Of:de implementatie van de SpiderMonkey Debugger (en de opschoning ervan)

We hebben de afgelopen twee jaar grote verbeteringen aangebracht in JavaScript-foutopsporing in Firefox DevTools. Feedback van ontwikkelaars heeft ons werk op het gebied van prestaties, bronkaarten, betrouwbaarheid van stappen, mooie afdrukken en meer soorten breekpunten geïnformeerd en gevalideerd. Dank je. Als je Firefox al een tijdje niet hebt geprobeerd voor het debuggen van modern JavaScript, is dit het juiste moment.

Veel van de bovengenoemde inspanningen waren gericht op de Debugger-frontend (geschreven in React en Redux). We konden gestaag vooruitgang boeken. Door de integratie met SpiderMonkey, de JavaScript-engine van Firefox, ging het werk langzamer. Om grotere functies aan te pakken, zoals de juiste asynchrone call-stacks (nu beschikbaar in DevEdition), moesten we een grote schoonmaak uitvoeren. Hier is hoe we dat deden.

Achtergrond:een korte geschiedenis van de JS Debugger

De JavaScript-foutopsporing in Firefox is gebaseerd op de Debugger . van de SpiderMonkey-engine API. Deze API is in 2011 toegevoegd. Sindsdien heeft het de toevoeging van vier JIT-compilers, de pensionering van twee ervan en de toevoeging van een WebAssembly-compiler overleefd. Dat alles, zonder dat er substantiële wijzigingen moeten worden aangebracht aan de gebruikers van de API. Debugger legt slechts tijdelijk een prestatiestraf op, terwijl de ontwikkelaar de uitvoering van de debuggee nauwlettend in de gaten houdt. Zodra de ontwikkelaar wegkijkt, kan het programma terugkeren naar zijn geoptimaliseerde paden.

Een paar belangrijke beslissingen (sommige van ons, andere opgelegd door de situatie) waren van invloed op de Debugger 's implementatie:

  • Voor beter of slechter, het is een centraal principe van de architectuur van Firefox dat JavaScript-code met verschillende privilegeniveaus een enkele hoop kan delen. Objectranden en functieaanroepen overschrijden indien nodig privilegegrenzen. SpiderMonkey's compartimenten zorg ervoor dat de nodige veiligheidscontroles worden uitgevoerd in deze vrij draaiende omgeving. De API moet naadloos over compartimentgrenzen heen werken.
  • Debugger is een intra-thread debugging API:gebeurtenissen in de debuggee worden afgehandeld op dezelfde thread die ze heeft geactiveerd. Dit houdt de implementatie vrij van threading-problemen, maar roept andere soorten complicaties op.
  • Debugger s moeten een natuurlijke interactie hebben met het ophalen van afval. Als een object niet wordt gemist, moet het mogelijk zijn voor de vuilnisman om het te recyclen, of het nu een Debugger is , een debuggee of anderszins.
  • Een Debugger mag alleen activiteit observeren die plaatsvindt binnen het bereik van een bepaalde set globale JavaScript-objecten (bijvoorbeeld een venster of een sandbox). Het zou geen effect moeten hebben op activiteit elders in de browser. Maar het moet ook mogelijk zijn voor meerdere Debugger s om hetzelfde globaal te observeren, zonder al te veel interferentie.

Vuilnisophaling

Mensen verklaren vuilnismannen meestal door te zeggen dat ze objecten recyclen die "onbereikbaar" zijn, maar dit is niet helemaal correct. Stel dat we bijvoorbeeld schrijven:

fetch("https://www.example.com/")
  .then(res => {
    res.body.getReader().closed.then(() => console.log("stream closed!"))
  });

Als we klaar zijn met het uitvoeren van deze instructie, is geen van de objecten die het heeft geconstrueerd bereikbaar voor de rest van het programma. Desalniettemin verbiedt de WHATWG-specificatie de browser om alles te verzamelen en de fetch te beëindigen . Als dit het geval zou zijn, zou het bericht niet op de console worden vastgelegd en zou de gebruiker weten dat de garbagecollection had plaatsgevonden.

Vuilnisverzamelaars houden zich aan een interessant principe:een object mag alleen worden gerecycled als het nooit zou worden gemist. Dat wil zeggen, het geheugen van een object mag alleen worden hergebruikt als dit geen waarneembaar effect heeft op de toekomstige uitvoering van het programma, behalve natuurlijk dat er meer geheugen beschikbaar komt voor verder gebruik.

Het principe in actie

Overweeg de volgende code:

// Create a new JavaScript global object, in its own compartment.
var global = newGlobal({ newCompartment: true });

// Create a new Debugger, and use its `onEnterFrame` hook to report function
// calls in `global`.
new Debugger(global).onEnterFrame = (frame) => {
  if (frame.callee) {
    console.log(`called function ${frame.callee.name}`);
  }
};

global.eval(`
  function f() { }
  function g() { f(); }
  g();
`);

Wanneer uitgevoerd in de JavaScript-shell van SpiderMonkey (waarin Debugger constructor en de newGlobal functie zijn onmiddellijk beschikbaar), dit drukt:

called function g
called function f

Net als in de fetch voorbeeld, de nieuwe Debugger wordt onbereikbaar door het programma zodra we klaar zijn met het instellen van de onEnterFrame haak. Aangezien echter alle toekomstige functieaanroepen binnen het bereik van global console-uitvoer zal produceren, zou het onjuist zijn voor de vuilnisman om de Debugger . te verwijderen . De afwezigheid ervan zou waarneembaar zijn zodra global een functie-aanroep gedaan.

Een soortgelijke redenering geldt voor veel andere Debugger faciliteiten. De onNewScript hook rapporteert de introductie van nieuwe code in het bereik van een debuggee global, hetzij door eval aan te roepen , het laden van een <script> element, het instellen van een onclick behandelaar of iets dergelijks. Of, door een breekpunt in te stellen, wordt de handlerfunctie ervan aangeroepen telkens wanneer de besturing het aangewezen punt in de code bereikt. In al deze gevallen roept debuggee-activiteit functies aan die zijn geregistreerd met een Debugger , die alles kan doen wat de ontwikkelaar wil, en dus waarneembare effecten heeft.

Dit geval is echter anders:

var global = newGlobal({ newCompartment: true });

new Debugger(global);

global.eval(`
  function f() { }
  function g() { f(); }
  g();
`);

Hier, de nieuwe Debugger is gemaakt, maar wordt verwijderd zonder dat er haken worden ingesteld. Als deze Debugger werden weggegooid, zou niemand er ooit wijzer van worden. Het moet in aanmerking komen voor recycling door de vuilnisman. Verder gaan, in de onEnterFrame voorbeeld hierboven, als global onnodig wordt, zonder timers of gebeurtenishandlers of wachtende ophaalacties om er ooit weer code in uit te voeren, dan global , het is Debugger , en zijn handlerfunctie moeten allemaal in aanmerking komen voor verzameling.

Het principe is dat Debugger objecten zijn niets bijzonders voor de GC. Het zijn gewoon objecten waarmee we de uitvoering van een JavaScript-programma kunnen observeren en verder dezelfde regels volgen als alle anderen. JavaScript-ontwikkelaars stellen het op prijs te weten dat, als ze simpelweg onnodige verstrikkingen vermijden, het systeem het geheugen voor hen zal opruimen zodra dit veilig is. En dit gemak strekt zich uit tot code met behulp van de Debugger API.

De implementatie

Als we de bovenstaande beschrijving doornemen, lijkt het duidelijk dat wanneer een Debugger heeft een onEnterFrame haak, een onNewScript hook, of iets dergelijks, zijn debuggee-globalen bevatten een eigen verwijzing ernaar. Zolang die globals in leven zijn, is de Debugger moet ook behouden blijven. Het wissen van al die hooks zou die eigendomsreferentie moeten verwijderen. De levendigheid van het globale garandeert dus niet langer dat de Debugger zal overleven. (Referenties van elders in het systeem kunnen natuurlijk.)

En dat is ongeveer hoe het is gedaan. Op C++-niveau heeft elke JavaScript-global een bijbehorende JS::Realm object, dat een tabel van DebuggerLink . bezit objecten, één voor elke Debugger waarvan het een debuggee is. Elke DebuggerLink object bevat een optioneel sterke verwijzing naar zijn Debugger . Dit wordt ingesteld wanneer de Debugger heeft interessante haken, en anders gewist. Daarom, wanneer de Debugger heeft haken ingesteld, er is een sterk pad, via de DebuggerLink tussenpersoon, van zijn debuggee-globalen tot de Debugger . Als de haken daarentegen vrij zijn, is er geen pad.

Een onderbrekingspunt dat in een script is ingesteld, gedraagt ​​zich op dezelfde manier. Het fungeert als een eigendomsreferentie van dat script naar de handlerfunctie van het breekpunt en de Debugger waartoe het behoort. Zolang het script live is, zullen de handler en Debugger moet ook in leven blijven. Of, als het script wordt hergebruikt, zal dat breekpunt zeker nooit meer worden geraakt, dus de handler kan net zo goed ook gaan. En als alle de Debugger 's breakpoints'-scripts worden gerecycled, waarna de scripts de Debugger niet langer beschermen uit collectie.

De zaken waren echter niet altijd zo eenvoudig.

Wat is er veranderd

Oorspronkelijk Debugger objecten hadden een enabled vlag, die, indien ingesteld op false , onmiddellijk alle Debugger uitgeschakeld 's haken en breekpunten. De bedoeling was om één centraal controlepunt te bieden. Op deze manier zou de Firefox Developer Tools-server een Debugger . kunnen neutraliseren (bijvoorbeeld wanneer de gereedschapskist gesloten is), zodat het geen verdere impact op het systeem heeft. Natuurlijk, gewoon het wissen van de Debugger De set debuggee-globalen - een mogelijkheid die we sowieso voor andere doeleinden nodig hadden - heeft bijna precies hetzelfde effect. Dit betekende dus de enabled vlag was overbodig. Maar, zo redeneerden we, hoeveel problemen kan een simpele booleaanse vlag eigenlijk veroorzaken?

Wat we niet hadden verwacht, was dat de aanwezigheid van de enabled flag maakte de hierboven beschreven eenvoudige implementatie onpraktisch. Moet enabled instellen tot false echt alle breekpunten in de scripts van de debuggee opruimen? En zou het terug moeten zetten op true ga je ze allemaal terugzetten? Dat leek belachelijk.

Dus, in plaats van globals en scripts te behandelen alsof ze referenties hadden naar hun geïnteresseerde Debugger s, hebben we een nieuwe fase toegevoegd aan het afvalinzamelingsproces. Zodra de verzamelaar zoveel mogelijk objecten had gevonden om te behouden, zouden we alle Debugger . doorlopen s in het systeem. We zouden iedereen willen vragen:Zijn een van uw debuggees er zeker van dat ze behouden blijven? Heb je haken of breekpunten ingesteld? En bent u ingeschakeld? Zo ja, dan hebben we de Debugger . gemarkeerd zelf voor retentie.

Natuurlijk hebben we ooit besloten om een ​​Debugger , moesten we ook alle objecten behouden die het of zijn handlerfuncties mogelijk zouden kunnen gebruiken. We zouden dus het proces voor het verzamelen van afval opnieuw starten, het een tweede keer laten uitputten en de scan van alle Debuggers herhalen. .

Vuilnisophaling opruimen

In het najaar van 2019 hebben Logan Smyth, Jason Laster en ik een reeks debugger-opruimingen uitgevoerd. Deze code, genaamd Debugger::markIteratively , was een van onze doelen. We hebben de enabled . verwijderd vlag, introduceerde (onder andere) de hierboven beschreven eigendomsranden en verkleinde Debugger::markIteratively tot het punt dat het veilig kan worden verwijderd. Dit werk is gearchiveerd als bug 1592158:"Verwijder Debugger::hasAnyLiveFrames en zijn gemene handlangers". (In feite heeft Logan het bij een stiekeme aanval verwijderd als onderdeel van een patch voor een blocker, bug 1592116.)

De SpiderMonkey-teamleden die verantwoordelijk zijn voor de vuilnisophaler, waardeerden ook onze opruiming. Het verwijderde een harig speciaal geval van de vuilnisman. De vervanging is code die er veel meer uitziet en zich gedraagt ​​zoals al het andere in SpiderMonkey. Het idee dat “dit daarop wijst; dus als we dit houden, kunnen we dat maar beter ook houden' is het standaardpad voor een vuilnisophaler. En zo werd dit werk Debugger van hoofdpijn in (bijna) gewoon een ander soort object.

Compartimenten

De Debugger API leverde de beheerders van de vuilnismannen ook andere problemen op, in de interactie met SpiderMonkey-compartimenten en -zones.

In Firefox bevat de JavaScript-heap over het algemeen een mix van objecten van verschillende privilegeniveaus en oorsprong. Chrome-objecten kunnen verwijzen naar inhoudsobjecten en vice versa. Uiteraard moet Firefox bepaalde regels afdwingen over hoe deze objecten op elkaar inwerken. Het is bijvoorbeeld mogelijk dat inhoudscode alleen bepaalde methoden voor een Chrome-object mag aanroepen. Of Chrome-code wil misschien alleen de originele, door de webstandaard gespecificeerde methoden van een object zien, ongeacht hoe de inhoud met het prototype heeft gespeeld of de eigenschappen ervan opnieuw heeft geconfigureerd.

(Merk op dat Firefox' lopende 'Fission'-project webinhoud van verschillende oorsprongen zal scheiden in verschillende processen, dus randen tussen oorsprongen zullen veel minder gebruikelijk worden. Maar zelfs na Fission zal er nog steeds interactie zijn tussen chrome en inhoud JavaScript-code.)

Runtimes, zones en rijken

Om deze controles uit te voeren, afvalinzameling te ondersteunen en het web te ondersteunen zoals gespecificeerd, verdeelt Firefox de JavaScript-wereld als volgt:

  • Een complete wereld van JavaScript-objecten die met elkaar kunnen interageren, wordt een runtime genoemd .
  • De objecten van een runtime zijn verdeeld in zones , dat zijn de eenheden van garbagecollection. Elke garbagecollection verwerkt een bepaalde set zones. Meestal is er één zone per browsertabblad.
  • Elke zone is verdeeld in compartimenten , die eenheden van oorsprong of privilege zijn. Alle objecten in een bepaald compartiment hebben dezelfde oorsprong en hetzelfde privilegeniveau.
  • Een compartiment is verdeeld in rijken , overeenkomend met JavaScript-vensterobjecten of andere soorten globale objecten zoals sandboxen of JSM's.

Elk script is toegewezen aan een bepaald rijk, afhankelijk van hoe het is geladen. En aan elk object wordt een rijk toegewezen, afhankelijk van het script waarmee het wordt gemaakt.

Scripts en objecten mogen alleen direct verwijzen naar objecten in hun eigen compartiment. Voor referenties tussen compartimenten houdt elk compartiment een verzameling gespecialiseerde proxy's bij, genaamd omslagen tussen compartimenten . Elk van deze wikkels vertegenwoordigt een specifiek object in een ander compartiment. Deze wrappers onderscheppen alle toegangen tot eigendommen en functieaanroepen en voeren veiligheidscontroles uit. Dit wordt gedaan om te beslissen of ze moeten doorgaan, op basis van de relatieve privilegeniveaus en oorsprong van het compartiment van de verpakking en het compartiment van de referent. In plaats van een object van het ene compartiment naar het andere door te geven of terug te brengen, zoekt SpiderMonkey de verpakking van dat object op in het bestemmingscompartiment (maakt het als er geen bestaat). Dan overhandigt het de verpakking in plaats van het object.

Compartimenten inpakken

Een uitgebreid systeem van beweringen, in de vuilnisophaler maar ook in de rest van SpiderMonkey, verifieert dat er nooit directe randen tussen compartimenten worden gecreëerd. Bovendien mogen scripts alleen objecten in hun eigen compartimenten rechtstreeks raken.

Maar aangezien elke referentie tussen compartimenten door een wikkel moet worden onderschept, vormen de wikkeltabellen van de compartimenten een handig register van alle inter-zone ook referenties. Dit is precies de informatie die de vuilnisman nodig heeft om één set zones apart van de rest te verzamelen. Als een object geen wikkels heeft die het representeren in compartimenten buiten zijn eigen zone, dan weet de verzamelaar het. En dat allemaal zonder de hele looptijd te hoeven bekijken. Geen enkele andere zone zou dat object missen als het zou worden gerecycled.

Foutopsporing tussen compartimenten

De Debugger API's Debugger.Object objecten gooien een sleutel in deze nette machine. Aangezien de debugger-server geprivilegieerde Chrome-code is en de debuggee meestal inhoudscode is, vallen deze in afzonderlijke compartimenten. Dit betekent dat een Debugger.Object 's verwijzing naar zijn referentie is een referentie tussen compartimenten.

Maar de Debugger.Objects kunnen geen compartimentoverschrijdende wikkels zijn. Een compartiment kan veel Debugger . hebben objecten, die elk hun eigen kudde van Debugger.Objects . hebben , dus er kunnen veel Debugger.Objects . zijn verwijzend naar hetzelfde debuggee-object in een enkel compartiment. (Hetzelfde geldt voor Debugger.Script en andere API-objecten. We concentreren ons op Debugger.Object hier voor de eenvoud.)

Voorheen loste SpiderMonkey dit op door te eisen dat elke Debugger.Object worden gecombineerd met een speciale toegang tot de wikkeltafel van het compartiment. De opzoeksleutel van de tabel was niet alleen een vreemd object, maar een (Debugger , vreemd voorwerp) paar. Dit behield de invariant dat de wikkeltabellen van de compartimenten een record hadden van alle referenties tussen compartimenten.

Helaas vereisten deze inzendingen een speciale behandeling. Een gewone wikkel met meerdere compartimenten kan worden verwijderd als de voorwerpen van het compartiment daar niet meer naar wijzen, aangezien een gelijkwaardige wikkel op aanvraag kan worden geconstrueerd. Maar een Debugger.Object moet worden bewaard zolang de Debugger en referent leven. Een gebruiker kan een aangepaste eigenschap op een Debugger.Object . plaatsen of gebruik het als een sleutel in een zwakke kaart. Die gebruiker zou de eigenschap of zwakke kaartinvoer kunnen verwachten wanneer hij het corresponderende debuggee-object opnieuw tegenkomt. Er is ook speciale zorg vereist om ervoor te zorgen dat de wrapper-tabelitems op betrouwbare wijze worden gemaakt en verwijderd synchroon met Debugger.Object maken, zelfs als er fouten in het geheugen of andere onderbrekingen optreden.

Compartimenten opruimen

Als onderdeel van onze code-opschoning in de herfst van 2019 hebben we de speciale wikkeltabel-items verwijderd. Door simpelweg de Debugger . te raadplegen API's eigen tabellen van Debugger.Objects , hebben we de verwijzingen naar cross-compartimentverwijzingen voor de vuilnisophaler gewijzigd. Dit is Debugger -specifieke code, die we natuurlijk liever vermijden, maar de afspraak was ook Debugger -specifiek. De huidige aanpak is directer. Het lijkt meer op een gewone traceercode voor vuilnismannen. Hierdoor is een zorgvuldige synchronisatie tussen twee tabellen niet meer nodig.

Gedwongen retouren en uitzonderingen

Wanneer SpiderMonkey een Debugger aanroept API-hook om een ​​soort activiteit in de debuggee te rapporteren, de meeste hooks kunnen een hervattingswaarde retourneren om te zeggen hoe de debuggee moet doorgaan met uitvoeren:

  • undefined betekent dat de debuggee normaal zou moeten doorgaan, alsof er niets was gebeurd.
  • Een object retourneren van de vorm { throw: EXN } betekent dat de debuggee moet doorgaan alsof de waarde EXN werden als uitzondering gegooid.
  • Een object retourneren van de vorm { return: RETVAL } betekent dat de debuggee onmiddellijk moet terugkeren van de functie die nu wordt uitgevoerd, met RETVAL als de retourwaarde.
  • null betekent dat de debuggee moet worden beëindigd, als door de trage scriptdialoog.

In de C++-code van SpiderMonkey was er een opgesomd type met de naam ResumeMode , die waarden had Continue , Throw , Return , en Terminate , die elk van deze mogelijkheden vertegenwoordigen. Elke site in SpiderMonkey die een gebeurtenis moest melden aan Debugger en respecteer dan een hervattingswaarde die nodig is om een ​​switch . te hebben verklaring voor elk van deze gevallen. De code in de bytecode-interpreter voor het invoeren van een functieaanroep zag er bijvoorbeeld als volgt uit:

switch (DebugAPI::onEnterFrame(cx, activation.entryFrame())) {
  case ResumeMode::Continue:
    break;
  case ResumeMode::Return:
    if (!ForcedReturn(cx, REGS)) {
      goto error;
    }
    goto successful_return_continuation;
  case ResumeMode::Throw:
  case ResumeMode::Terminate:
    goto error;
  default:
    MOZ_CRASH("bad DebugAPI::onEnterFrame resume mode");
}

Relevante SpiderMonkey-conventies ontdekken

Logan Smyth merkte echter dat, behalve voor ResumeMode::Return , vielen al deze gevallen al onder SpiderMonkey's conventie voor 'feilbare operaties'. Volgens deze conventie moet een C++-functie die zou kunnen mislukken een JSContext* . accepteren argument, en retourneer een bool waarde. Als de bewerking slaagt, moet deze true . teruggeven; anders zou het false moeten retourneren en stel de status in van de gegeven JSContext om een ​​gegenereerde uitzondering of een beëindiging aan te geven.

Aangezien JavaScript-objecten bijvoorbeeld proxy's kunnen zijn of getter-eigenschappen kunnen hebben, is het ophalen van een eigenschap van een object een feilbare bewerking. Dus SpiderMonkey's js::GetProperty functie heeft de handtekening:

bool js::GetProperty(JSContext* cx,
                     HandleValue v, HandlePropertyName name,
                     MutableHandleValue vp);

De waarde v is het object, en name is de naam van de eigenschap die we ervan willen ophalen. Bij succes, GetProperty slaat de waarde op in vp en retourneert true . Bij mislukking vertelt het cx wat er mis ging, en retourneert false . Code die deze functie aanroept, kan er als volgt uitzien:

if (!GetProperty(cx, obj, id, &value)) {
  return false; // propagate failure to our caller
}

Allerlei functies in SpiderMonkey volgen deze conventie. Ze kunnen zo complex zijn als het evalueren van een script, of zo eenvoudig als het toewijzen van een object. (Sommige functies retourneren een nullptr in plaats van een bool , maar het principe is hetzelfde.)

Deze conventie omvat drie van de vier ResumeMode waarden:

  • ResumeMode::Continue is gelijk aan het retourneren van true .
  • ResumeMode::Throw is gelijk aan het retourneren van false en een uitzondering instellen op de JSContext .
  • ResumeMode::Terminate is gelijk aan het retourneren van false maar geen uitzondering instellen op de JSContext .

Het enige geval dat dit niet ondersteunt is ResumeMode::Return .

Voortbouwen op SpiderMonkey-conventies

Vervolgens merkte Logan op dat SpiderMonkey al verantwoordelijk is voor het rapporteren van alle stackframe-pops aan de DebugAPI::onLeaveFrame functie, zodat Debugger kan frame onPop oproepen handlers en het uitvoeren van andere boekhouding. Dus om een ​​onmiddellijke terugkeer af te dwingen, kunnen we in principe:

  • de gewenste retourwaarde ergens opbergen;
  • retour false zonder een uitzondering te maken om beëindiging te forceren;
  • wacht tot de beëindiging zich verspreidt via de huidige functieaanroep, waarna SpiderMonkey DebugAPI::onLeaveFrame aanroept;
  • herstel onze opgeslagen retourwaarde en bewaar deze op de juiste plaats in het stapelframe; en tot slot
  • retour true alsof er niets was gebeurd, een gewone terugkeer nabootsen.

Met deze aanpak zou de ResumeMode . niet nodig zijn opsomming of speciale afhandeling op DebugAPI bel sites. De gewone regels van SpiderMonkey voor het verhogen en verspreiden van uitzonderingen zijn al heel bekend bij elke SpiderMonkey-ontwikkelaar. Die regels doen al het werk voor ons.

Het blijkt dat de machinerie voor het opbergen van de retourwaarde en het herkennen van de noodzaak van interventie in DebugAPI::onLeaveFrame bestond al in SpiderMonkey. Shu-Yu Guo had het jaren geleden geïmplementeerd om een ​​zeldzaam geval van trage scripttime-outs en single-stepping af te handelen.

Met deze verzameling inzichten was Logan in staat om de oproepsites waarop SpiderMonkey activiteit rapporteert om te zetten in Debugger in oproepsites net als die van elke andere feilbare functie. De oproep naar DebugAPI::onEnterFrame hierboven weergegeven luidt nu eenvoudig:

if (!DebugAPI::onEnterFrame(cx, activation.entryFrame())) {
  goto error;
}

Andere opruimacties

We hebben een aantal andere kleine schoonmaakacties uitgevoerd als onderdeel van onze inspanningen in het najaar van 2019:

  • We splitsen het bestand js/src/vm/Debugger.cpp , oorspronkelijk 14k regels lang en bevat de volledige Debugger implementatie, in acht afzonderlijke bronbestanden, en verplaatste ze naar de map js/src/debugger . Phabricator weigert niet langer om het bestand in te kleuren vanwege de lengte.
  • Elke Debugger API-objecttype, Debugger.Object , Debugger.Frame , Debugger.Environment , Debugger.Script , en Debugger.Source , wordt nu vertegenwoordigd door zijn eigen C++-subklasse van js::NativeObject . Hierdoor kunnen we de organisatorische hulpmiddelen gebruiken die C++ biedt om hun implementatiecode te structureren en te reikwijdte. We kunnen ook dynamische typecontroles in de C++-code vervangen door typen. De compiler kan deze tijdens het compileren controleren.
  • De code waarmee Debugger.Script en Debugger.Source verwijzen naar zowel JavaScript als WebAssembly-code werd vereenvoudigd zodat Debugger::wrapVariantReferent , in plaats van vijf sjabloonparameters te vereisen, vereist slechts één - en één die kan worden afgeleid door de C++-compiler, om op te starten.

Ik geloof dat dit werk heeft geresulteerd in een substantiële verbetering van de kwaliteit van leven van ingenieurs die te maken hebben met Debugger ’s uitvoering. Ik hoop dat het Firefox de komende jaren effectief kan blijven bedienen.