tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

analyzeRoots.js (35704B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
      6 
      7 "use strict";
      8 
      9 loadRelativeToScript('utility.js');
     10 loadRelativeToScript('annotations.js');
     11 loadRelativeToScript('callgraph.js');
     12 loadRelativeToScript('CFG.js');
     13 loadRelativeToScript('dumpCFG.js');
     14 
     15 var sourceRoot = (os.getenv('SOURCE') || '') + '/';
     16 
     17 var functionName;
     18 var functionBodies;
     19 
     20 try {
     21    var options = parse_options([
     22        {
     23            name: "--function",
     24            type: 'string',
     25        },
     26        {
     27            name: "-f",
     28            type: "string",
     29            dest: "function",
     30        },
     31        {
     32            name: "gcFunctions",
     33            default: "gcFunctions.lst"
     34        },
     35        {
     36            name: "limitedFunctions",
     37            default: "limitedFunctions.lst"
     38        },
     39        {
     40            name: "gcTypes",
     41            default: "gcTypes.txt"
     42        },
     43        {
     44            name: "typeInfo",
     45            default: "typeInfo.txt"
     46        },
     47        {
     48            name: "batch",
     49            type: "number",
     50            default: 1
     51        },
     52        {
     53            name: "numBatches",
     54            type: "number",
     55            default: 1
     56        },
     57        {
     58            name: "tmpfile",
     59            default: "tmp.txt"
     60        },
     61    ]);
     62 } catch (e) {
     63    printErr(e);
     64    printErr("Usage: analyzeRoots.js [-f function_name] <gcFunctions.lst> <limitedFunctions.lst> <gcTypes.txt> <typeInfo.txt> [start end [tmpfile]]");
     65    quit(1);
     66 }
     67 var gcFunctions = {};
     68 var text = snarf(options.gcFunctions).split("\n");
     69 assert(text.pop().length == 0);
     70 for (const line of text)
     71    gcFunctions[mangled(line)] = readable(line);
     72 
     73 var limitedFunctions = JSON.parse(snarf(options.limitedFunctions));
     74 text = null;
     75 
     76 var typeInfo = loadTypeInfo(options.typeInfo);
     77 
     78 var match;
     79 var gcThings = new Set();
     80 var gcPointers = new Set();
     81 var gcRefs = new Set(typeInfo.GCRefs);
     82 
     83 text = snarf(options.gcTypes).split("\n");
     84 for (var line of text) {
     85    if (match = /^GCThing: (.*)/.exec(line))
     86        gcThings.add(match[1]);
     87    if (match = /^GCPointer: (.*)/.exec(line))
     88        gcPointers.add(match[1]);
     89 }
     90 text = null;
     91 
     92 function isGCRef(type)
     93 {
     94    if (type.Kind == "CSU")
     95        return gcRefs.has(type.Name);
     96    return false;
     97 }
     98 
     99 function isGCType(type)
    100 {
    101    if (type.Kind == "CSU")
    102        return gcThings.has(type.Name);
    103    else if (type.Kind == "Array")
    104        return isGCType(type.Type);
    105    return false;
    106 }
    107 
    108 function isUnrootedPointerDeclType(decl)
    109 {
    110    // Treat non-temporary T& references as if they were the underlying type T.
    111    // For now, restrict this to only the types specifically annotated with JS_HAZ_GC_REF
    112    // to avoid lots of false positives with other types.
    113    let type = isReferenceDecl(decl) && isGCRef(decl.Type.Type) ? decl.Type.Type : decl.Type;
    114 
    115    while (type.Kind == "Array") {
    116        type = type.Type;
    117    }
    118 
    119    if (type.Kind == "Pointer") {
    120        return isGCType(type.Type);
    121    } else if (type.Kind == "CSU") {
    122        return gcPointers.has(type.Name);
    123    } else {
    124        return false;
    125    }
    126 }
    127 
    128 function edgeCanGC(functionName, body, edge, scopeAttrs, functionBodies)
    129 {
    130    if (edge.Kind != "Call") {
    131        return false;
    132    }
    133 
    134    for (const { callee, attrs } of getCallees(body, edge, scopeAttrs, functionBodies)) {
    135        if (attrs & (ATTR_GC_SUPPRESSED | ATTR_REPLACED)) {
    136            continue;
    137        }
    138 
    139        if (callee.kind == "direct") {
    140            const func = mangled(callee.name);
    141            if ((func in gcFunctions) || ((func + internalMarker) in gcFunctions))
    142                return `'${func}$${gcFunctions[func]}'`;
    143            return false;
    144        } else if (callee.kind == "indirect") {
    145            if (!indirectCallCannotGC(functionName, callee.variable)) {
    146                return "'*" + callee.variable + "'";
    147            }
    148        } else if (callee.kind == "field") {
    149            if (fieldCallCannotGC(callee.staticCSU, callee.field)) {
    150                continue;
    151            }
    152            const fieldkey = callee.fieldKey;
    153            if (fieldkey in gcFunctions) {
    154                return `'${fieldkey}'`;
    155            }
    156        } else {
    157            return "<unknown>";
    158        }
    159    }
    160 
    161    return false;
    162 }
    163 
    164 // Search upwards through a function's control flow graph (CFG) to find a path containing:
    165 //
    166 // - a use of a variable, preceded by
    167 //
    168 // - a function call that can GC, preceded by
    169 //
    170 // - a use of the variable that shows that the live range starts at least that
    171 //   far back, preceded by
    172 //
    173 // - an informative use of the variable (which might be the same use), one that
    174 //   assigns to it a value that might contain a GC pointer (or is the start of
    175 //   the function for parameters or 'this'.) This is not necessary for
    176 //   correctness, it just makes it easier to understand why something might be
    177 //   a hazard. The output of the analysis will include the whole path from the
    178 //   informative use to the post-GC use, to make the problem as understandable
    179 //   as possible.
    180 //
    181 // A canonical example might be:
    182 //
    183 //     void foo() {
    184 //         JS::Value* val = lookupValue(); <-- informative use
    185 //         if (!val.isUndefined()) {       <-- any use
    186 //             GC();                       <-- GC call
    187 //         }
    188 //         putValue(val);                  <-- a use after a GC
    189 //     }
    190 //
    191 // The search is performed on an underlying CFG that we traverse in
    192 // breadth-first order (to find the shortest path). We build a path starting
    193 // from an empty path and conditionally lengthening and improving it according
    194 // to the computation occurring on each incoming edge. (If that path so far
    195 // does not have a GC call and we traverse an edge with a GC call, then we
    196 // lengthen the path by that edge and record it as including a GC call.) The
    197 // resulting path may include a point or edge more than once! For example, in:
    198 //
    199 //     void foo(JS::Value val) {
    200 //         for (int i = 0; i < N; i++) {
    201 //             GC();
    202 //             val = processValue(val);
    203 //         }
    204 //     }
    205 //
    206 // the path would start at the point after processValue(), go through the GC(),
    207 // then back to the processValue() (for the call in the previous loop
    208 // iteration).
    209 //
    210 // While searching, each point is annotated with a path node corresponding to
    211 // the best path found to that node so far. When a later search ends up at the
    212 // same point, the best path node is kept. (But the path that it heads may
    213 // include an earlier path node for the same point, as in the case above.)
    214 //
    215 // What info we want depends on whether the variable turns out to be live
    216 // across a GC call. We are looking for both hazards (unrooted variables live
    217 // across GC calls) and unnecessary roots (rooted variables that have no GC
    218 // calls in their live ranges.)
    219 //
    220 // If not:
    221 //
    222 //  - 'minimumUse': the earliest point in each body that uses the variable, for
    223 //    reporting on unnecessary roots.
    224 //
    225 // If so:
    226 //
    227 //  - 'successor': a path from the GC call to a use of the variable after the GC
    228 //    call, chained through 'successor' field in the returned edge descriptor
    229 //
    230 //  - 'gcInfo': a direct pointer to the GC call edge
    231 //
    232 function findGCBeforeValueUse(start_body, start_point, funcAttrs, variable)
    233 {
    234    // Scan through all edges preceding an unrooted variable use, using an
    235    // explicit worklist, looking for a GC call and a preceding point where the
    236    // variable is known to be live. A worklist contains an incoming edge
    237    // together with a description of where it or one of its successors GC'd
    238    // (if any).
    239 
    240    class Path {
    241        get ProgressProperties() { return ["informativeUse", "anyUse", "gcInfo"]; }
    242 
    243        constructor(successor_path, body, ppoint) {
    244            Object.assign(this, {body, ppoint});
    245            if (successor_path !== undefined) {
    246                this.successor = successor_path;
    247                for (const prop of this.ProgressProperties) {
    248                    if (prop in successor_path) {
    249                        this[prop] = successor_path[prop];
    250                    }
    251                }
    252            }
    253        }
    254 
    255        toString() {
    256            const trail = [];
    257            for (let path = this; path.ppoint; path = path.successor) {
    258                trail.push(path.ppoint);
    259            }
    260            return trail.join();
    261        }
    262 
    263        // Return -1, 0, or 1 to indicate how complete this Path is compared
    264        // to another one.
    265        compare(other) {
    266            for (const prop of this.ProgressProperties) {
    267                const a = this.hasOwnProperty(prop);
    268                const b = other.hasOwnProperty(prop);
    269                if (a != b) {
    270                    return a - b;
    271                }
    272            }
    273            return 0;
    274        }
    275    };
    276 
    277    // In case we never find an informative use, keep track of the best path
    278    // found with any use.
    279    let bestPathWithAnyUse = null;
    280 
    281    const visitor = new class extends Visitor {
    282        constructor() {
    283            super(functionBodies);
    284        }
    285 
    286        // Do a BFS upwards through the CFG, starting from a use of the
    287        // variable and searching for a path containing a GC followed by an
    288        // initializing use of the variable (or, in forward direction, a start
    289        // of the variable's live range, a GC within that live range, and then
    290        // a use showing that the live range extends past the GC call.)
    291        // Actually, possibly two uses: any use at all, and then if available
    292        // an "informative" use that is more convincing (they may be the same).
    293        //
    294        // The CFG is a graph (a 'body' here is acyclic, but they can contain
    295        // loop nodes that bridge to additional bodies for the loop, so the
    296        // overall graph can by cyclic.) That means there may be multiple paths
    297        // from point A to point B, and we want paths with a GC on them. This
    298        // can be thought of as searching for a "maximal GCing" path from a use
    299        // A to an initialization B.
    300        //
    301        // This is implemented as a BFS search that when it reaches a point
    302        // that has been visited before, stops if and only if the current path
    303        // being advanced is a less GC-ful path. The traversal pushes a
    304        // `gcInfo` token, initially empty, up through the graph and stores the
    305        // maximal one visited so far at every point.
    306        //
    307        // Note that this means we may traverse through the same point more
    308        // than once, and so in theory this scan is superlinear -- if you visit
    309        // every point twice, once for a non GC path and once for a GC path, it
    310        // would be 2^n. But that is unlikely to matter, since you'd need lots
    311        // of split/join pairs that GC on one side and not the other, and you'd
    312        // have to visit them in an unlucky order. This could be fixed by
    313        // updating the gcInfo for past points in a path when a GC is found,
    314        // but it hasn't been found to matter in practice yet.
    315 
    316        next_action(prev, current) {
    317            // Continue if first visit, or the new path is more complete than the old path. This
    318            // could be enhanced at some point to choose paths with 'better'
    319            // examples of GC (eg a call that invokes GC through concrete functions rather than going through a function pointer that is conservatively assumed to GC.)
    320 
    321            if (!current) {
    322                // This search path has been terminated.
    323                return "prune";
    324            }
    325 
    326            if (current.informativeUse) {
    327                // We have a path with an informative use leading to a GC
    328                // leading to the starting point.
    329                assert(current.gcInfo);
    330                return "done";
    331            }
    332 
    333            if (prev === undefined) {
    334                // first visit
    335                return "continue";
    336            }
    337 
    338            if (!prev.gcInfo && current.gcInfo) {
    339                // More GC.
    340                return "continue";
    341            } else {
    342                return "prune";
    343            }
    344        }
    345 
    346        merge_info(prev, current) {
    347            // Keep the most complete path.
    348 
    349            if (!prev || !current) {
    350                return prev || current;
    351            }
    352 
    353            // Tie goes to the first found, since it will be shorter when doing a BFS-like search.
    354            return prev.compare(current) >= 0 ? prev : current;
    355        }
    356 
    357        extend_path(edge, body, ppoint, successor_path) {
    358            // Clone the successor path node and then tack on the new point. Other values
    359            // will be updated during the rest of this function, according to what is
    360            // happening on the edge.
    361            const path = new Path(successor_path, body, ppoint);
    362            if (edge === null) {
    363                // Artificial edge to connect loops to their surrounding nodes in the outer body.
    364                // Does not influence "completeness" of path.
    365                return path;
    366            }
    367 
    368            assert(ppoint == edge.Index[0]);
    369 
    370            if (edgeEndsValueLiveRange(edge, variable, body)) {
    371                // Terminate the search through this point.
    372                return null;
    373            }
    374 
    375            const edge_starts = edgeStartsValueLiveRange(edge, variable);
    376            const edge_uses = edgeUsesVariable(edge, variable, body);
    377 
    378            if (edge_starts || edge_uses) {
    379                if (!body.minimumUse || ppoint < body.minimumUse)
    380                    body.minimumUse = ppoint;
    381            }
    382 
    383            if (edge_starts) {
    384                // This is a beginning of the variable's live range. If we can
    385                // reach a GC call from here, then we're done -- we have a path
    386                // from the beginning of the live range, through the GC call, to a
    387                // use after the GC call that proves its live range extends at
    388                // least that far.
    389                if (path.gcInfo) {
    390                    path.anyUse = path.anyUse || edge;
    391                    path.informativeUse = path.informativeUse || edge;
    392                    return path;
    393                }
    394 
    395                // Otherwise, truncate this particular branch of the search at this
    396                // edge -- there is no GC after this use, and traversing the edge
    397                // would lead to a different live range.
    398                return null;
    399            }
    400 
    401            // The value is live across this edge. Check whether this edge can
    402            // GC (if we don't have a GC yet on this path.)
    403            const had_gcInfo = Boolean(path.gcInfo);
    404            const edgeAttrs = body.attrs[ppoint] | funcAttrs;
    405            if (!path.gcInfo && !(edgeAttrs & (ATTR_GC_SUPPRESSED | ATTR_REPLACED))) {
    406                var gcName = edgeCanGC(functionName, body, edge, edgeAttrs, functionBodies);
    407                if (gcName) {
    408                    path.gcInfo = {name:gcName, body, ppoint, edge: edge.Index};
    409                }
    410            }
    411 
    412            // Beginning of function?
    413            if (ppoint == body.Index[0] && body.BlockId.Kind != "Loop") {
    414                if (path.gcInfo && (variable.Kind == "Arg" || variable.Kind == "This")) {
    415                    // The scope of arguments starts at the beginning of the
    416                    // function.
    417                    path.anyUse = path.informativeUse = true;
    418                }
    419 
    420                if (path.anyUse) {
    421                    // We know the variable was live across the GC. We may or
    422                    // may not have found an "informative" explanation
    423                    // beginning of the live range. (This can happen if the
    424                    // live range started when a variable is used as a
    425                    // retparam.)
    426                    return path;
    427                }
    428            }
    429 
    430            if (!path.gcInfo) {
    431                // We haven't reached a GC yet, so don't start looking for uses.
    432                return path;
    433            }
    434 
    435            if (!edge_uses) {
    436                // We have a GC. If this edge doesn't use the value, then there
    437                // is no change to the completeness of the path.
    438                return path;
    439            }
    440 
    441            // The live range starts at least this far back, so we're done for
    442            // the same reason as with edge_starts. The only difference is that
    443            // a GC on this edge indicates a hazard, whereas if we're killing a
    444            // live range in the GC call then it's not live *across* the call.
    445            //
    446            // However, we may want to generate a longer usage chain for the
    447            // variable than is minimally necessary. For example, consider:
    448            //
    449            //   Value v = f();
    450            //   if (v.isUndefined())
    451            //     return false;
    452            //   gc();
    453            //   return v;
    454            //
    455            // The call to .isUndefined() is considered to be a use and
    456            // therefore indicates that v must be live at that point. But it's
    457            // more helpful to the user to continue the 'successor' path to
    458            // include the ancestor where the value was generated. So we will
    459            // only stop here if edge.Kind is Assign; otherwise, we'll pass a
    460            // "preGCLive" value up through the worklist to remember that the
    461            // variable *is* alive before the GC and so this function should be
    462            // returning a true value even if we don't find an assignment.
    463 
    464            // One special case: if the use of the variable is on the
    465            // destination part of the edge (which currently only happens for
    466            // the return value and a terminal edge in the body), and this edge
    467            // is also GCing, then that usage happens *after* the GC and so
    468            // should not be used for anyUse or informativeUse. This matters
    469            // for a hazard involving a destructor GC'ing after an immobile
    470            // return value has been assigned:
    471            //
    472            //   GCInDestructor guard(cx);
    473            //   if (cond()) {
    474            //     return nullptr;
    475            //   }
    476            //
    477            // which boils down to
    478            //
    479            //   p1 --(construct guard)-->
    480            //   p2 --(call cond)-->
    481            //   p3 --(returnval := nullptr) -->
    482            //   p4 --(destruct guard, possibly GCing)-->
    483            //   p5
    484            //
    485            // The return value is considered to be live at p5. The live range
    486            // of the return value would ordinarily be from p3->p4->p5, except
    487            // that the nullptr assignment means it needn't be considered live
    488            // back that far, and so the live range is *just* p5. The GC on the
    489            // 4->5 edge happens just before that range, so the value was not
    490            // live across the GC.
    491            //
    492            if (!had_gcInfo && edge_uses == edge.Index[1]) {
    493                return path; // New GC does not cross this variable use.
    494            }
    495 
    496            path.anyUse = path.anyUse || edge;
    497            bestPathWithAnyUse = bestPathWithAnyUse || path;
    498            if (edge.Kind == 'Assign') {
    499                path.informativeUse = edge; // Done! Setting this terminates the search.
    500            }
    501 
    502            return path;
    503        };
    504    };
    505 
    506    const result = BFS_upwards(start_body, start_point, functionBodies, visitor, new Path());
    507    if (result && result.gcInfo && result.anyUse) {
    508        return result;
    509    } else {
    510        return bestPathWithAnyUse;
    511    }
    512 }
    513 
    514 function variableLiveAcrossGC(funcAttrs, variable, liveToEnd=false)
    515 {
    516    // A variable is live across a GC if (1) it is used by an edge (as in, it
    517    // was at least initialized), and (2) it is used after a GC in a successor
    518    // edge.
    519 
    520    for (var body of functionBodies)
    521        body.minimumUse = 0;
    522 
    523    for (var body of functionBodies) {
    524        if (!("PEdge" in body))
    525            continue;
    526        for (var edge of body.PEdge) {
    527            // Examples:
    528            //
    529            //   JSObject* obj = NewObject();
    530            //   cangc();
    531            //   obj = NewObject();     <-- mentions 'obj' but kills previous value
    532            //
    533            // This is not a hazard. Contrast this with:
    534            //
    535            //   JSObject* obj = NewObject();
    536            //   cangc();
    537            //   obj = LookAt(obj);  <-- uses 'obj' and kills previous value
    538            //
    539            // This is a hazard; the initial value of obj is live across
    540            // cangc(). And a third possibility:
    541            //
    542            //   JSObject* obj = NewObject();
    543            //   obj = CopyObject(obj);
    544            //
    545            // This is not a hazard, because even though CopyObject can GC, obj
    546            // is not live across it. (obj is live before CopyObject, and
    547            // probably after, but not across.) There may be a hazard within
    548            // CopyObject, of course.
    549            //
    550 
    551            // Ignore uses that are just invalidating the previous value.
    552            if (edgeEndsValueLiveRange(edge, variable, body))
    553                continue;
    554 
    555            var usePoint = edgeUsesVariable(edge, variable, body, liveToEnd);
    556            if (usePoint) {
    557                var call = findGCBeforeValueUse(body, usePoint, funcAttrs, variable);
    558                if (!call)
    559                    continue;
    560 
    561                call.afterGCUse = usePoint;
    562                return call;
    563            }
    564        }
    565    }
    566    return null;
    567 }
    568 
    569 // An unrooted variable has its address stored in another variable via
    570 // assignment, or passed into a function that can GC. If the address is
    571 // assigned into some other variable, we can't track it to see if it is held
    572 // live across a GC. If it is passed into a function that can GC, then it's
    573 // sort of like a Handle to an unrooted location, and the callee could GC
    574 // before overwriting it or rooting it.
    575 function unsafeVariableAddressTaken(funcAttrs, variable)
    576 {
    577    for (var body of functionBodies) {
    578        if (!("PEdge" in body))
    579            continue;
    580        for (var edge of body.PEdge) {
    581            if (edgeTakesVariableAddress(edge, variable, body)) {
    582                if (funcAttrs & (ATTR_GC_SUPPRESSED | ATTR_REPLACED)) {
    583                    continue;
    584                }
    585                if (edge.Kind == "Assign" || edgeCanGC(functionName, body, edge, funcAttrs, functionBodies)) {
    586                    return {body:body, ppoint:edge.Index[0]};
    587                }
    588            }
    589        }
    590    }
    591    return null;
    592 }
    593 
    594 // Read out the brief (non-JSON, semi-human-readable) CFG description for the
    595 // given function and store it.
    596 function loadPrintedLines(functionName)
    597 {
    598    assert(!os.system("xdbfind src_body.xdb '" + functionName + "' > " + options.tmpfile));
    599    var lines = snarf(options.tmpfile).split('\n');
    600 
    601    for (var body of functionBodies)
    602        body.lines = [];
    603 
    604    // Distribute lines of output to the block they originate from.
    605    var currentBody = null;
    606    for (var line of lines) {
    607        if (/^block:/.test(line)) {
    608            if (match = /:(loop#[\d#]+)/.exec(line)) {
    609                var loop = match[1];
    610                var found = false;
    611                for (var body of functionBodies) {
    612                    if (body.BlockId.Kind == "Loop" && body.BlockId.Loop == loop) {
    613                        assert(!found);
    614                        found = true;
    615                        currentBody = body;
    616                    }
    617                }
    618                assert(found);
    619            } else {
    620                for (var body of functionBodies) {
    621                    if (body.BlockId.Kind == "Function")
    622                        currentBody = body;
    623                }
    624            }
    625        }
    626        if (currentBody)
    627            currentBody.lines.push(line);
    628    }
    629 }
    630 
    631 function findLocation(body, ppoint, opts={brief: false})
    632 {
    633    var location = body.PPoint[ppoint ? ppoint - 1 : 0].Location;
    634    var file = location.CacheString;
    635 
    636    if (file.indexOf(sourceRoot) == 0)
    637        file = file.substring(sourceRoot.length);
    638 
    639    if (opts.brief) {
    640        var m = /.*\/(.*)/.exec(file);
    641        if (m)
    642            file = m[1];
    643    }
    644 
    645    return file + ":" + location.Line;
    646 }
    647 
    648 function locationLine(text)
    649 {
    650    if (match = /:(\d+)$/.exec(text))
    651        return match[1];
    652    return 0;
    653 }
    654 
    655 function getEntryTrace(functionName, entry)
    656 {
    657    const trace = [];
    658 
    659    var gcPoint = entry.gcInfo ? entry.gcInfo.ppoint : 0;
    660 
    661    if (!functionBodies[0].lines)
    662        loadPrintedLines(functionName);
    663 
    664    while (entry.successor) {
    665        var ppoint = entry.ppoint;
    666        var lineText = findLocation(entry.body, ppoint, {"brief": true});
    667 
    668        var edgeText = "";
    669        if (entry.successor && entry.successor.body == entry.body) {
    670            // If the next point in the trace is in the same block, look for an
    671            // edge between them.
    672            var next = entry.successor.ppoint;
    673 
    674            if (!entry.body.edgeTable) {
    675                var table = {};
    676                entry.body.edgeTable = table;
    677                for (var line of entry.body.lines) {
    678                    if (match = /^\w+\((\d+,\d+),/.exec(line))
    679                        table[match[1]] = line; // May be multiple?
    680                }
    681                if (entry.body.BlockId.Kind == 'Loop') {
    682                    const [startPoint, endPoint] = entry.body.Index;
    683                    table[`${endPoint},${startPoint}`] = '(loop to next iteration)';
    684                }
    685            }
    686 
    687            edgeText = entry.body.edgeTable[ppoint + "," + next];
    688            assert(edgeText);
    689            if (ppoint == gcPoint)
    690                edgeText += " [[GC call]]";
    691        } else {
    692            // Look for any outgoing edge from the chosen point.
    693            for (var line of entry.body.lines) {
    694                if (match = /\((\d+),/.exec(line)) {
    695                    if (match[1] == ppoint) {
    696                        edgeText = line;
    697                        break;
    698                    }
    699                }
    700            }
    701            if (ppoint == entry.body.Index[1] && entry.body.BlockId.Kind == "Function")
    702                edgeText += " [[end of function]]";
    703        }
    704 
    705        // TODO: Store this in a more structured form for better markup, and perhaps
    706        // linking to line numbers.
    707        trace.push({lineText, edgeText});
    708        entry = entry.successor;
    709    }
    710 
    711    return trace;
    712 }
    713 
    714 function isRootedDeclType(decl)
    715 {
    716    // Treat non-temporary T& references as if they were the underlying type T.
    717    const type = isReferenceDecl(decl) ? decl.Type.Type : decl.Type;
    718    return type.Kind == "CSU" && ((type.Name in typeInfo.RootedPointers) ||
    719                                  (type.Name in typeInfo.RootedGCThings));
    720 }
    721 
    722 function printRecord(record) {
    723    print(JSON.stringify(record));
    724 }
    725 
    726 function processBodies(functionName, wholeBodyAttrs)
    727 {
    728    if (!("DefineVariable" in functionBodies[0]))
    729      return;
    730    const funcInfo = limitedFunctions[mangled(functionName)] || { attributes: 0 };
    731    const funcAttrs = funcInfo.attributes | wholeBodyAttrs;
    732 
    733    // Look for the JS_EXPECT_HAZARDS annotation, so as to output a different
    734    // message in that case that won't be counted as a hazard.
    735    var annotations = new Set();
    736    for (const variable of functionBodies[0].DefineVariable) {
    737        if (variable.Variable.Kind == "Func" && variable.Variable.Name[0] == functionName) {
    738            for (const { Name: [tag, value] } of (variable.Type.Annotation || [])) {
    739                if (tag == 'annotate')
    740                    annotations.add(value);
    741            }
    742        }
    743    }
    744 
    745    let missingExpectedHazard = annotations.has("Expect Hazards");
    746 
    747    // Awful special case, hopefully temporary:
    748    //
    749    // The DOM bindings code generator uses "holders" to externally root
    750    // variables. So for example:
    751    //
    752    //       StringObjectRecordOrLong arg0;
    753    //       StringObjectRecordOrLongArgument arg0_holder(arg0);
    754    //       arg0_holder.TrySetToStringObjectRecord(cx, args[0]);
    755    //       GC();
    756    //       self->PassUnion22(cx, arg0);
    757    //
    758    // This appears to be a rooting hazard on arg0, but it is rooted by
    759    // arg0_holder if you set it to any of its union types that requires
    760    // rooting.
    761    //
    762    // Additionally, the holder may be reported as a hazard because it's not
    763    // itself a Rooted or a subclass of AutoRooter; it contains a
    764    // Maybe<RecordRooter<T>> that will get emplaced if rooting is required.
    765    //
    766    // Hopefully these will be simplified at some point (see bug 1517829), but
    767    // for now we special-case functions in the mozilla::dom namespace that
    768    // contain locals with types ending in "Argument". Or
    769    // Maybe<SomethingArgument>. Or Maybe<SpiderMonkeyInterfaceRooter<T>>. It's
    770    // a harsh world.
    771    const ignoreVars = new Set();
    772    if (functionName.match(/mozilla::dom::/)) {
    773        const vars = functionBodies[0].DefineVariable.filter(
    774            v => v.Type.Kind == 'CSU' && v.Variable.Kind == 'Local'
    775        ).map(
    776            v => [ v.Variable.Name[0], v.Type.Name ]
    777        );
    778 
    779        const holders = vars.filter(
    780            ([n, t]) => n.match(/^arg\d+_holder$/) &&
    781                        (t.includes("Argument") || t.includes("Rooter")));
    782        for (const [holder,] of holders) {
    783            ignoreVars.add(holder); // Ignore the holder.
    784            ignoreVars.add(holder.replace("_holder", "")); // Ignore the "managed" arg.
    785        }
    786    }
    787 
    788    const [mangledSymbol, readable] = splitFunction(functionName);
    789 
    790    for (let decl of functionBodies[0].DefineVariable) {
    791        var name;
    792        if (decl.Variable.Kind == "This")
    793            name = "this";
    794        else if (decl.Variable.Kind == "Return")
    795            name = "<returnvalue>";
    796        else
    797            name = decl.Variable.Name[0];
    798 
    799        if (ignoreVars.has(name))
    800            continue;
    801 
    802        let liveToEnd = false;
    803        if (decl.Variable.Kind == "Arg" && isReferenceDecl(decl) && decl.Type.Reference == 2) {
    804            // References won't run destructors, so they would normally not be
    805            // considered live at the end of the function. In order to handle
    806            // the pattern of moving a GC-unsafe value into a function (eg an
    807            // AutoCheckCannotGC&&), assume all argument rvalue references live to the
    808            // end of the function unless their liveness is terminated by
    809            // calling reset() or moving them into another function call.
    810            liveToEnd = true;
    811        }
    812 
    813        if (isRootedDeclType(decl)) {
    814            if (!variableLiveAcrossGC(funcAttrs, decl.Variable)) {
    815                // The earliest use of the variable should be its constructor.
    816                var lineText;
    817                for (var body of functionBodies) {
    818                    if (body.minimumUse) {
    819                        var text = findLocation(body, body.minimumUse);
    820                        if (!lineText || locationLine(lineText) > locationLine(text))
    821                            lineText = text;
    822                    }
    823                }
    824                const record = {
    825                    record: "unnecessary",
    826                    functionName,
    827                    mangled: mangledSymbol,
    828                    readable,
    829                    variable: name,
    830                    type: str_Type(decl.Type),
    831                    loc: lineText || "???",
    832                }
    833                print(",");
    834                printRecord(record);
    835            }
    836        } else if (isUnrootedPointerDeclType(decl)) {
    837            var result = variableLiveAcrossGC(funcAttrs, decl.Variable, liveToEnd);
    838            if (result) {
    839                assert(result.gcInfo);
    840                const edge = result.gcInfo.edge;
    841                const body = result.gcInfo.body;
    842                const lineText = findLocation(body, result.gcInfo.ppoint);
    843                const makeLoc = l => [l.Location.CacheString, l.Location.Line];
    844                const range = [makeLoc(body.PPoint[edge[0] - 1]), makeLoc(body.PPoint[edge[1] - 1])];
    845                const record = {
    846                    record: "unrooted",
    847                    expected: annotations.has("Expect Hazards"),
    848                    functionName,
    849                    mangled: mangledSymbol,
    850                    readable,
    851                    variable: name,
    852                    type: str_Type(decl.Type),
    853                    gccall: result.gcInfo.name.replaceAll("'", ""),
    854                    gcrange: range,
    855                    loc: lineText,
    856                    trace: getEntryTrace(functionName, result),
    857                };
    858                missingExpectedHazard = false;
    859                print(",");
    860                printRecord(record);
    861            }
    862            result = unsafeVariableAddressTaken(funcAttrs, decl.Variable);
    863            if (result) {
    864                var lineText = findLocation(result.body, result.ppoint);
    865                const record = {
    866                    record: "address",
    867                    functionName,
    868                    mangled: mangledSymbol,
    869                    readable,
    870                    variable: name,
    871                    loc: lineText,
    872                    trace: getEntryTrace(functionName, {body:result.body, ppoint:result.ppoint}),
    873                };
    874                print(",");
    875                printRecord(record);
    876            }
    877        }
    878    }
    879 
    880    if (missingExpectedHazard) {
    881        const {
    882            Location: [
    883                { CacheString: startfile, Line: startline },
    884                { CacheString: endfile, Line: endline }
    885            ]
    886        } = functionBodies[0];
    887 
    888        const loc = (startfile == endfile) ? `${startfile}:${startline}-${endline}`
    889              : `${startfile}:${startline}`;
    890 
    891        const record = {
    892            record: "missing",
    893            functionName,
    894            mangled: mangledSymbol,
    895            readable,
    896            loc,
    897        }
    898        print(",");
    899        printRecord(record);
    900    }
    901 }
    902 
    903 print("[\n");
    904 var now = new Date();
    905 printRecord({record: "time", iso: "" + now, t: now.getTime()});
    906 
    907 var xdb = xdbLibrary();
    908 xdb.open("src_body.xdb");
    909 
    910 var minStream = xdb.min_data_stream()|0;
    911 var maxStream = xdb.max_data_stream()|0;
    912 
    913 var start = batchStart(options.batch, options.numBatches, minStream, maxStream);
    914 var end = batchLast(options.batch, options.numBatches, minStream, maxStream);
    915 
    916 function process(name, json) {
    917    functionName = name;
    918    functionBodies = JSON.parse(json);
    919 
    920    // Annotate body with a table of all points within the body that may be in
    921    // a limited scope (eg within the scope of a GC suppression RAII class.)
    922    // body.attrs is a plain object indexed by point, with the value being a
    923    // bit set stored in an integer.
    924    for (var body of functionBodies)
    925        body.attrs = [];
    926 
    927    for (var body of functionBodies) {
    928        for (var [pbody, id, attrs] of allRAIIGuardedCallPoints(typeInfo, functionBodies, body, isLimitConstructor))
    929        {
    930            if (attrs)
    931                pbody.attrs[id] = attrs;
    932        }
    933    }
    934 
    935    processBodies(functionName);
    936 }
    937 
    938 if (options.function) {
    939    var data = xdb.read_entry(options.function);
    940    var json = data.readString();
    941    debugger;
    942    process(options.function, json);
    943    xdb.free_string(data);
    944    print("\n]\n");
    945    quit(0);
    946 }
    947 
    948 for (var nameIndex = start; nameIndex <= end; nameIndex++) {
    949    var name = xdb.read_key(nameIndex);
    950    var functionName = name.readString();
    951    var data = xdb.read_entry(name);
    952    xdb.free_string(name);
    953    var json = data.readString();
    954    try {
    955        process(functionName, json);
    956    } catch (e) {
    957        printErr("Exception caught while handling " + functionName);
    958        throw(e);
    959    }
    960    xdb.free_string(data);
    961 }
    962 
    963 print("\n]\n");