| /* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| |
| "use strict"; |
| |
| load('utility.js'); |
| load('annotations.js'); |
| load('suppressedPoints.js'); |
| |
| var subclasses = {}; |
| var superclasses = {}; |
| var classFunctions = {}; |
| |
| function addClassEntry(index, name, other) |
| { |
| if (!(name in index)) { |
| index[name] = [other]; |
| return; |
| } |
| |
| for (var entry of index[name]) { |
| if (entry == other) |
| return; |
| } |
| |
| index[name].push(other); |
| } |
| |
| function processCSU(csuName, csu) |
| { |
| if (!("FunctionField" in csu)) |
| return; |
| for (var field of csu.FunctionField) { |
| if (1 in field.Field) { |
| var superclass = field.Field[1].Type.Name; |
| var subclass = field.Field[1].FieldCSU.Type.Name; |
| assert(subclass == csuName); |
| addClassEntry(subclasses, superclass, subclass); |
| addClassEntry(superclasses, subclass, superclass); |
| } |
| if ("Variable" in field) { |
| // Note: not dealing with overloading correctly. |
| var name = field.Variable.Name[0]; |
| var key = csuName + ":" + field.Field[0].Name[0]; |
| if (!(key in classFunctions)) |
| classFunctions[key] = []; |
| classFunctions[key].push(name); |
| } |
| } |
| } |
| |
| function findVirtualFunctions(csu, field) |
| { |
| var worklist = [csu]; |
| |
| // Virtual call targets on subclasses of nsISupports may be incomplete, |
| // if the interface is scriptable. Just treat all indirect calls on |
| // nsISupports objects as potentially GC'ing, except AddRef/Release |
| // which should never enter the JS engine (even when calling dtors). |
| while (worklist.length) { |
| var csu = worklist.pop(); |
| if (csu == "nsISupports") { |
| if (field == "AddRef" || field == "Release") |
| return []; |
| return null; |
| } |
| if (csu in superclasses) { |
| for (var superclass of superclasses[csu]) |
| worklist.push(superclass); |
| } |
| } |
| |
| var functions = []; |
| var worklist = [csu]; |
| |
| while (worklist.length) { |
| var csu = worklist.pop(); |
| var key = csu + ":" + field; |
| |
| if (key in classFunctions) { |
| for (var name of classFunctions[key]) |
| functions.push(name); |
| } |
| |
| if (csu in subclasses) { |
| for (var subclass of subclasses[csu]) |
| worklist.push(subclass); |
| } |
| } |
| |
| return functions; |
| } |
| |
| var memoized = {}; |
| var memoizedCount = 0; |
| |
| function memo(name) |
| { |
| if (!(name in memoized)) { |
| memoizedCount++; |
| memoized[name] = "" + memoizedCount; |
| print("#" + memoizedCount + " " + name); |
| } |
| return memoized[name]; |
| } |
| |
| var seenCallees = null; |
| var seenSuppressedCallees = null; |
| |
| function processBody(caller, body) |
| { |
| if (!('PEdge' in body)) |
| return; |
| for (var edge of body.PEdge) { |
| if (edge.Kind != "Call") |
| continue; |
| var callee = edge.Exp[0]; |
| var suppressText = ""; |
| var seen = seenCallees; |
| if (edge.Index[0] in body.suppressed) { |
| suppressText = "SUPPRESS_GC "; |
| seen = seenSuppressedCallees; |
| } |
| var prologue = suppressText + memo(caller) + " "; |
| if (callee.Kind == "Var") { |
| assert(callee.Variable.Kind == "Func"); |
| var name = callee.Variable.Name[0]; |
| if (!(name in seen)) { |
| print("D " + prologue + memo(name)); |
| seen[name] = true; |
| } |
| var otherName = otherDestructorName(name); |
| if (otherName && !(otherName in seen)) { |
| print("D " + prologue + memo(otherName)); |
| seen[otherName] = true; |
| } |
| } else { |
| assert(callee.Kind == "Drf"); |
| if (callee.Exp[0].Kind == "Fld") { |
| var field = callee.Exp[0].Field; |
| var fieldName = field.Name[0]; |
| var csuName = field.FieldCSU.Type.Name; |
| var functions = null; |
| if ("FieldInstanceFunction" in field) |
| functions = findVirtualFunctions(csuName, fieldName); |
| if (functions) { |
| // Known set of virtual call targets. |
| for (var name of functions) { |
| if (!(name in seen)) { |
| print("D " + prologue + memo(name)); |
| seen[name] = true; |
| } |
| } |
| } else { |
| // Unknown set of call targets. Non-virtual field call, |
| // or virtual call on an nsISupports object. |
| print("F " + prologue + "CLASS " + csuName + " FIELD " + fieldName); |
| } |
| } else if (callee.Exp[0].Kind == "Var") { |
| // indirect call through a variable. |
| print("I " + prologue + |
| "VARIABLE " + callee.Exp[0].Variable.Name[0]); |
| } else { |
| // unknown call target. |
| print("I " + prologue + "VARIABLE UNKNOWN"); |
| } |
| } |
| } |
| } |
| |
| var callgraph = {}; |
| |
| var xdb = xdbLibrary(); |
| xdb.open("src_comp.xdb"); |
| |
| var minStream = xdb.min_data_stream(); |
| var maxStream = xdb.max_data_stream(); |
| |
| for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) { |
| var csu = xdb.read_key(csuIndex); |
| var data = xdb.read_entry(csu); |
| var json = JSON.parse(data.readString()); |
| processCSU(csu.readString(), json[0]); |
| |
| xdb.free_string(csu); |
| xdb.free_string(data); |
| } |
| |
| xdb.open("src_body.xdb"); |
| |
| var minStream = xdb.min_data_stream(); |
| var maxStream = xdb.max_data_stream(); |
| |
| for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) { |
| var name = xdb.read_key(nameIndex); |
| var data = xdb.read_entry(name); |
| functionBodies = JSON.parse(data.readString()); |
| for (var body of functionBodies) |
| body.suppressed = []; |
| for (var body of functionBodies) |
| computeSuppressedPoints(body); |
| |
| seenCallees = {}; |
| seenSuppressedCallees = {}; |
| |
| for (var body of functionBodies) |
| processBody(name.readString(), body); |
| |
| xdb.free_string(name); |
| xdb.free_string(data); |
| } |