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");