tor-browser

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

analyzeHeapWrites.js (47434B)


      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('dumpCFG.js');
     13 
     14 ///////////////////////////////////////////////////////////////////////////////
     15 // Annotations
     16 ///////////////////////////////////////////////////////////////////////////////
     17 
     18 function checkExternalFunction(entry)
     19 {
     20    var whitelist = [
     21        "__builtin_clz",
     22        "__builtin_expect",
     23        "isprint",
     24        "ceilf",
     25        "floorf",
     26        /^rusturl/,
     27        "memcmp",
     28        "strcmp",
     29        "fmod",
     30        "floor",
     31        "ceil",
     32        "atof",
     33        /memchr/,
     34        "strlen",
     35        /Servo_DeclarationBlock_GetCssText/,
     36        "Servo_GetArcStringData",
     37        "Servo_IsWorkerThread",
     38        /nsIFrame::AppendOwnedAnonBoxes/,
     39        // Assume that atomic accesses are threadsafe.
     40        /^__atomic_/,
     41    ];
     42    if (entry.matches(whitelist))
     43        return;
     44 
     45    // memcpy and memset are safe if the target pointer is threadsafe.
     46    const simpleWrites = [
     47        "memcpy",
     48        "memset",
     49        "memmove",
     50    ];
     51 
     52    if (entry.isSafeArgument(1) && simpleWrites.includes(entry.name))
     53        return;
     54 
     55    dumpError(entry, null, "External function");
     56 }
     57 
     58 function hasThreadsafeReferenceCounts(entry, regexp)
     59 {
     60    // regexp should match some nsISupports-operating function and produce the
     61    // name of the nsISupports class via exec().
     62 
     63    // nsISupports classes which have threadsafe reference counting.
     64    var whitelist = [
     65        "nsIRunnable",
     66 
     67        // I don't know if these always have threadsafe refcounts.
     68        "nsAtom",
     69        "nsIPermissionManager",
     70        "nsIURI",
     71    ];
     72 
     73    var match = regexp.exec(entry.name);
     74    return match && nameMatchesArray(match[1], whitelist);
     75 }
     76 
     77 function checkOverridableVirtualCall(entry, location, callee)
     78 {
     79    // We get here when a virtual call is made on a structure which might be
     80    // overridden by script or by a binary extension. This includes almost
     81    // everything under nsISupports, however, so for the most part we ignore
     82    // this issue. The exception is for nsISupports AddRef/Release, which are
     83    // not in general threadsafe and whose overrides will not be generated by
     84    // the callgraph analysis.
     85    if (callee != "nsISupports.AddRef" && callee != "nsISupports.Release")
     86        return;
     87 
     88    if (hasThreadsafeReferenceCounts(entry, /::~?nsCOMPtr\(.*?\[with T = (.*?)\]$/))
     89        return;
     90    if (hasThreadsafeReferenceCounts(entry, /RefPtrTraits.*?::Release.*?\[with U = (.*?)\]/))
     91        return;
     92    if (hasThreadsafeReferenceCounts(entry, /nsCOMPtr<T>::assign_assuming_AddRef.*?\[with T = (.*?)\]/))
     93        return;
     94    if (hasThreadsafeReferenceCounts(entry, /nsCOMPtr<T>::assign_with_AddRef.*?\[with T = (.*?)\]/))
     95        return;
     96 
     97    // Watch for raw addref/release.
     98    var whitelist = [
     99        "Gecko_AddRefAtom",
    100        "Gecko_ReleaseAtom",
    101        /nsPrincipal::Get/,
    102    ];
    103    if (entry.matches(whitelist))
    104        return;
    105 
    106    dumpError(entry, location, "AddRef/Release on nsISupports");
    107 }
    108 
    109 function checkIndirectCall(entry, location, callee)
    110 {
    111    var name = entry.name;
    112 
    113    // These hash table callbacks should be threadsafe.
    114    if (/PLDHashTable/.test(name) && (/matchEntry/.test(callee) || /hashKey/.test(callee)))
    115        return;
    116    if (/PL_HashTable/.test(name) && /keyCompare/.test(callee))
    117        return;
    118 
    119    dumpError(entry, location, "Indirect call " + callee);
    120 }
    121 
    122 function checkVariableAssignment(entry, location, variable)
    123 {
    124    var name = entry.name;
    125 
    126    dumpError(entry, location, "Variable assignment " + variable);
    127 }
    128 
    129 // Annotations for function parameters, based on function name and parameter
    130 // name + type.
    131 function treatAsSafeArgument(entry, varName, csuName)
    132 {
    133    var whitelist = [
    134        // These iterator classes should all be thread local. They are passed
    135        // in to some Servo bindings and are created on the heap by others, so
    136        // just ignore writes to them.
    137        [null, null, /StyleChildrenIterator/],
    138        [null, null, /ExplicitChildIterator/],
    139 
    140        // The use of BeginReading() to instantiate this class confuses the
    141        // analysis.
    142        [null, null, /nsReadingIterator/],
    143 
    144        // These classes are passed to some Servo bindings to fill in.
    145        [/^Gecko_/, null, "nsStyleImageLayers"],
    146        [/^Gecko_/, null, /FontFamilyList/],
    147 
    148        // Various Servo binding out parameters. This is a mess and there needs
    149        // to be a way to indicate which params are out parameters, either using
    150        // an attribute or a naming convention.
    151        [/ClassOrClassList/, /aClass/, null],
    152        ["Gecko_GetAtomAsUTF16", "aLength", null],
    153        ["Gecko_CopyMozBindingFrom", "aDest", null],
    154        ["Gecko_SetNullImageValue", "aImage", null],
    155        ["Gecko_SetGradientImageValue", "aImage", null],
    156        ["Gecko_SetImageElement", "aImage", null],
    157        ["Gecko_SetLayerImageImageValue", "aImage", null],
    158        ["Gecko_CopyImageValueFrom", "aImage", null],
    159        ["Gecko_SetCursorArrayLength", "aStyleUI", null],
    160        ["Gecko_CopyCursorArrayFrom", "aDest", null],
    161        ["Gecko_SetCursorImageValue", "aCursor", null],
    162        ["Gecko_SetListStyleImageImageValue", "aList", null],
    163        ["Gecko_SetListStyleImageNone", "aList", null],
    164        ["Gecko_CopyListStyleImageFrom", "aList", null],
    165        ["Gecko_ClearStyleContents", "aContent", null],
    166        ["Gecko_CopyStyleContentsFrom", "aContent", null],
    167        ["Gecko_CopyStyleGridTemplateValues", "aGridTemplate", null],
    168        ["Gecko_ResetStyleCoord", null, null],
    169        ["Gecko_CopyClipPathValueFrom", "aDst", null],
    170        ["Gecko_DestroyClipPath", "aClip", null],
    171        ["Gecko_ResetFilters", "effects", null],
    172        [/Gecko_CSSValue_Set/, "aCSSValue", null],
    173        ["Gecko_CSSValue_Drop", "aCSSValue", null],
    174        ["Gecko_CSSFontFaceRule_GetCssText", "aResult", null],
    175        ["Gecko_EnsureTArrayCapacity", "aArray", null],
    176        ["Gecko_ClearPODTArray", "aArray", null],
    177        ["Gecko_SetStyleGridTemplate", "aGridTemplate", null],
    178        ["Gecko_ResizeTArrayForStrings", "aArray", null],
    179        ["Gecko_ClearAndResizeStyleContents", "aContent", null],
    180        [/Gecko_ClearAndResizeCounter/, "aContent", null],
    181        [/Gecko_CopyCounter.*?From/, "aContent", null],
    182        [/Gecko_SetContentDataImageValue/, "aList", null],
    183        [/Gecko_SetContentData/, "aContent", null],
    184        ["Gecko_SetCounterFunction", "aContent", null],
    185        [/Gecko_EnsureStyle.*?ArrayLength/, "aArray", null],
    186        ["Gecko_GetOrCreateKeyframeAtStart", "aKeyframes", null],
    187        ["Gecko_GetOrCreateInitialKeyframe", "aKeyframes", null],
    188        ["Gecko_GetOrCreateFinalKeyframe", "aKeyframes", null],
    189        ["Gecko_AppendPropertyValuePair", "aProperties", null],
    190        ["Gecko_SetStyleCoordCalcValue", null, null],
    191        ["Gecko_StyleClipPath_SetURLValue", "aClip", null],
    192        ["Gecko_nsStyleFilter_SetURLValue", "aEffects", null],
    193        ["Gecko_nsStyleSVG_SetDashArrayLength", "aSvg", null],
    194        ["Gecko_nsStyleSVG_CopyDashArray", "aDst", null],
    195        ["Gecko_nsStyleFont_SetLang", "aFont", null],
    196        ["Gecko_nsStyleFont_CopyLangFrom", "aFont", null],
    197        ["Gecko_ClearWillChange", "aDisplay", null],
    198        ["Gecko_AppendWillChange", "aDisplay", null],
    199        ["Gecko_CopyWillChangeFrom", "aDest", null],
    200        ["Gecko_InitializeImageCropRect", "aImage", null],
    201        ["Gecko_CopyShapeSourceFrom", "aDst", null],
    202        ["Gecko_DestroyShapeSource", "aShape", null],
    203        ["Gecko_StyleShapeSource_SetURLValue", "aShape", null],
    204        ["Gecko_NewBasicShape", "aShape", null],
    205        ["Gecko_NewShapeImage", "aShape", null],
    206        ["Gecko_nsFont_InitSystem", "aDest", null],
    207        ["Gecko_nsFont_SetFontFeatureValuesLookup", "aFont", null],
    208        ["Gecko_nsFont_ResetFontFeatureValuesLookup", "aFont", null],
    209        ["Gecko_nsStyleFont_FixupNoneGeneric", "aFont", null],
    210        ["Gecko_StyleTransition_SetUnsupportedProperty", "aTransition", null],
    211        ["Gecko_AddPropertyToSet", "aPropertySet", null],
    212        ["Gecko_CalcStyleDifference", "aAnyStyleChanged", null],
    213        ["Gecko_CalcStyleDifference", "aOnlyResetStructsChanged", null],
    214        ["Gecko_nsStyleSVG_CopyContextProperties", "aDst", null],
    215        ["Gecko_nsStyleFont_PrefillDefaultForGeneric", "aFont", null],
    216        ["Gecko_nsStyleSVG_SetContextPropertiesLength", "aSvg", null],
    217        ["Gecko_ClearAlternateValues", "aFont", null],
    218        ["Gecko_AppendAlternateValues", "aFont", null],
    219        ["Gecko_CopyAlternateValuesFrom", "aDest", null],
    220        ["Gecko_nsTArray_FontFamilyName_AppendNamed", "aNames", null],
    221        ["Gecko_nsTArray_FontFamilyName_AppendGeneric", "aNames", null],
    222    ];
    223    for (var [entryMatch, varMatch, csuMatch] of whitelist) {
    224        assert(entryMatch || varMatch || csuMatch);
    225        if (entryMatch && !nameMatches(entry.name, entryMatch))
    226            continue;
    227        if (varMatch && !nameMatches(varName, varMatch))
    228            continue;
    229        if (csuMatch && (!csuName || !nameMatches(csuName, csuMatch)))
    230            continue;
    231        return true;
    232    }
    233    return false;
    234 }
    235 
    236 function isSafeAssignment(entry, edge, variable)
    237 {
    238    if (edge.Kind != 'Assign')
    239        return false;
    240 
    241    var [mangled, unmangled] = splitFunction(entry.name);
    242 
    243    // The assignment
    244    //
    245    //   nsFont* font = fontTypes[eType];
    246    //
    247    // ends up with 'font' pointing to a member of 'this', so it should inherit
    248    // the safety of 'this'.
    249    if (unmangled.includes("mozilla::LangGroupFontPrefs::Initialize") &&
    250        variable == 'font')
    251    {
    252        const [lhs, rhs] = edge.Exp;
    253        const {Kind, Exp: [{Kind: indexKind, Exp: [collection, index]}]} = rhs;
    254        if (Kind == 'Drf' &&
    255            indexKind == 'Index' &&
    256            collection.Kind == 'Var' &&
    257            collection.Variable.Name[0] == 'fontTypes')
    258        {
    259            return entry.isSafeArgument(0); // 'this'
    260        }
    261    }
    262 
    263    return false;
    264 }
    265 
    266 function checkFieldWrite(entry, location, fields)
    267 {
    268    var name = entry.name;
    269    for (var field of fields) {
    270        // The analysis is having some trouble keeping track of whether
    271        // already_AddRefed and nsCOMPtr structures are safe to access.
    272        // Hopefully these will be thread local, but it would be better to
    273        // improve the analysis to handle these.
    274        if (/already_AddRefed.*?.mRawPtr/.test(field))
    275            return;
    276        if (/nsCOMPtr<.*?>.mRawPtr/.test(field))
    277            return;
    278 
    279        if (/\bThreadLocal<\b/.test(field))
    280            return;
    281    }
    282 
    283    var str = "";
    284    for (var field of fields)
    285        str += " " + field;
    286 
    287    dumpError(entry, location, "Field write" + str);
    288 }
    289 
    290 function checkDereferenceWrite(entry, location, variable)
    291 {
    292    var name = entry.name;
    293 
    294    // Maybe<T> uses placement new on local storage in a way we don't understand.
    295    // Allow this if the Maybe<> value itself is threadsafe.
    296    if (/Maybe.*?::emplace/.test(name) && entry.isSafeArgument(0))
    297        return;
    298 
    299    // UniquePtr writes through temporaries referring to its internal storage.
    300    // Allow this if the UniquePtr<> is threadsafe.
    301    if (/UniquePtr.*?::reset/.test(name) && entry.isSafeArgument(0))
    302        return;
    303 
    304    // Operations on nsISupports reference counts.
    305    if (hasThreadsafeReferenceCounts(entry, /nsCOMPtr<T>::swap\(.*?\[with T = (.*?)\]/))
    306        return;
    307 
    308    // ConvertToLowerCase::write writes through a local pointer into the first
    309    // argument.
    310    if (/ConvertToLowerCase::write/.test(name) && entry.isSafeArgument(0))
    311        return;
    312 
    313    dumpError(entry, location, "Dereference write " + (variable ? variable : "<unknown>"));
    314 }
    315 
    316 function ignoreCallEdge(entry, callee)
    317 {
    318    var name = entry.name;
    319 
    320    // nsPropertyTable::GetPropertyInternal has the option of removing data
    321    // from the table, but when it is called by nsPropertyTable::GetProperty
    322    // this will not occur.
    323    if (/nsPropertyTable::GetPropertyInternal/.test(callee) &&
    324        /nsPropertyTable::GetProperty/.test(name))
    325    {
    326        return true;
    327    }
    328 
    329    // Document::PropertyTable calls GetExtraPropertyTable (which has side
    330    // effects) if the input category is non-zero. If a literal zero was passed
    331    // in for the category then we treat it as a safe argument, per
    332    // isEdgeSafeArgument, so just watch for that.
    333    if (/Document::GetExtraPropertyTable/.test(callee) &&
    334        /Document::PropertyTable/.test(name) &&
    335        entry.isSafeArgument(1))
    336    {
    337        return true;
    338    }
    339 
    340    // CachedBorderImageData is exclusively owned by nsStyleImage, but the
    341    // analysis is not smart enough to know this.
    342    if (/CachedBorderImageData::PurgeCachedImages/.test(callee) &&
    343        /nsStyleImage::/.test(name) &&
    344        entry.isSafeArgument(0))
    345    {
    346        return true;
    347    }
    348 
    349    // StyleShapeSource exclusively owns its UniquePtr<nsStyleImage>.
    350    if (/nsStyleImage::SetURLValue/.test(callee) &&
    351        /StyleShapeSource::SetURL/.test(name) &&
    352        entry.isSafeArgument(0))
    353    {
    354        return true;
    355    }
    356 
    357    // The AddRef through a just-assigned heap pointer here is not handled by
    358    // the analysis.
    359    if (/nsCSSValue::Array::AddRef/.test(callee) &&
    360        /nsStyleContentData::SetCounters/.test(name) &&
    361        entry.isSafeArgument(2))
    362    {
    363        return true;
    364    }
    365 
    366    // AllChildrenIterator asks AppendOwnedAnonBoxes to append into an nsTArray
    367    // local variable.
    368    if (/nsIFrame::AppendOwnedAnonBoxes/.test(callee) &&
    369        /AllChildrenIterator::AppendNativeAnonymousChildren/.test(name))
    370    {
    371        return true;
    372    }
    373 
    374    // Runnables are created and named on one thread, then dispatched
    375    // (possibly to another). Writes on the origin thread are ok.
    376    if (/::SetName/.test(callee) &&
    377        /::UnlabeledDispatch/.test(name))
    378    {
    379        return true;
    380    }
    381 
    382    // We manually lock here
    383    if (name == "Gecko_nsFont_InitSystem" ||
    384        name == "Gecko_GetFontMetrics" ||
    385        name == "Gecko_nsStyleFont_FixupMinFontSize" ||
    386        /ThreadSafeGetDefaultFontHelper/.test(name))
    387    {
    388        return true;
    389    }
    390 
    391    return false;
    392 }
    393 
    394 function ignoreContents(entry)
    395 {
    396    var whitelist = [
    397        // We don't care what happens when we're about to crash.
    398        "abort",
    399        /MOZ_ReportAssertionFailure/,
    400        /MOZ_ReportCrash/,
    401        /MOZ_Crash/,
    402        /MOZ_CrashPrintf/,
    403        /AnnotateMozCrashReason/,
    404        /InvalidArrayIndex_CRASH/,
    405        /NS_ABORT_OOM/,
    406 
    407        // These ought to be threadsafe.
    408        "NS_DebugBreak",
    409        /mozalloc_handle_oom/,
    410        /^NS_Log/, /log_print/, /LazyLogModule::operator/,
    411        /SprintfLiteral/, "PR_smprintf", "PR_smprintf_free",
    412        /NS_DispatchToMainThread/, /NS_ReleaseOnMainThread/,
    413        /NS_NewRunnableFunction/, /NS_Atomize/,
    414        /nsCSSValue::BufferFromString/,
    415        /NS_xstrdup/,
    416        /Assert_NoQueryNeeded/,
    417        /AssertCurrentThreadOwnsMe/,
    418        /PlatformThread::CurrentId/,
    419        /imgRequestProxy::GetProgressTracker/, // Uses an AutoLock
    420        /Smprintf/,
    421        "malloc",
    422        "calloc",
    423        "free",
    424        "realloc",
    425        "memalign",
    426        "strdup",
    427        "strndup",
    428        "moz_xmalloc",
    429        "moz_xcalloc",
    430        "moz_xrealloc",
    431        "moz_xmemalign",
    432        "moz_xstrdup",
    433        "moz_xstrndup",
    434        "jemalloc_thread_local_arena",
    435 
    436        // These all create static strings in local storage, which is threadsafe
    437        // to do but not understood by the analysis yet.
    438        / EmptyString\(\)/,
    439 
    440        // These could probably be handled by treating the scope of PSAutoLock
    441        // aka BaseAutoLock<PSMutex> as threadsafe.
    442        /profiler_register_thread/,
    443        /profiler_unregister_thread/,
    444 
    445        // The analysis thinks we'll write to mBits in the DoGetStyleFoo<false>
    446        // call.  Maybe the template parameter confuses it?
    447        /ComputedStyle::PeekStyle/,
    448 
    449        // The analysis can't cope with the indirection used for the objects
    450        // being initialized here, from nsCSSValue::Array::Create to the return
    451        // value of the Item(i) getter.
    452        /nsCSSValue::SetCalcValue/,
    453 
    454        // Unable to analyze safety of linked list initialization.
    455        "Gecko_NewCSSValueSharedList",
    456        "Gecko_CSSValue_InitSharedList",
    457 
    458        // Unable to trace through dataflow, but straightforward if inspected.
    459        "Gecko_NewNoneTransform",
    460 
    461        // Need main thread assertions or other fixes.
    462        /EffectCompositor::GetServoAnimationRule/,
    463    ];
    464    if (entry.matches(whitelist))
    465        return true;
    466 
    467    if (entry.isSafeArgument(0)) {
    468        var heapWhitelist = [
    469            // Operations on heap structures pointed to by arrays and strings are
    470            // threadsafe as long as the array/string itself is threadsafe.
    471            /nsTArray_Impl.*?::AppendElement/,
    472            /nsTArray_Impl.*?::RemoveElementsAt/,
    473            /nsTArray_Impl.*?::ReplaceElementsAt/,
    474            /nsTArray_Impl.*?::InsertElementAt/,
    475            /nsTArray_Impl.*?::SetCapacity/,
    476            /nsTArray_Impl.*?::SetLength/,
    477            /nsTArray_base.*?::EnsureCapacity/,
    478            /nsTArray_base.*?::ShiftData/,
    479            /AutoTArray.*?::Init/,
    480            /(nsTSubstring<T>|nsAC?String)::SetCapacity/,
    481            /(nsTSubstring<T>|nsAC?String)::SetLength/,
    482            /(nsTSubstring<T>|nsAC?String)::Assign/,
    483            /(nsTSubstring<T>|nsAC?String)::Append/,
    484            /(nsTSubstring<T>|nsAC?String)::Replace/,
    485            /(nsTSubstring<T>|nsAC?String)::Trim/,
    486            /(nsTSubstring<T>|nsAC?String)::Truncate/,
    487            /(nsTSubstring<T>|nsAC?String)::StripTaggedASCII/,
    488            /(nsTSubstring<T>|nsAC?String)::operator=/,
    489            /nsTAutoStringN<T, N>::nsTAutoStringN/,
    490 
    491            // Similar for some other data structures
    492            /nsCOMArray_base::SetCapacity/,
    493            /nsCOMArray_base::Clear/,
    494            /nsCOMArray_base::AppendElement/,
    495 
    496            // UniquePtr is similar.
    497            /mozilla::UniquePtr/,
    498 
    499            // The use of unique pointers when copying mCropRect here confuses
    500            // the analysis.
    501            /nsStyleImage::DoCopy/,
    502        ];
    503        if (entry.matches(heapWhitelist))
    504            return true;
    505    }
    506 
    507    if (entry.isSafeArgument(1)) {
    508        var firstArgWhitelist = [
    509            /nsTextFormatter::snprintf/,
    510            /nsTextFormatter::ssprintf/,
    511            /_ASCIIToUpperInSitu/,
    512 
    513            // Handle some writes into an array whose safety we don't have a good way
    514            // of tracking currently.
    515            /FillImageLayerList/,
    516            /FillImageLayerPositionCoordList/,
    517        ];
    518        if (entry.matches(firstArgWhitelist))
    519            return true;
    520    }
    521 
    522    if (entry.isSafeArgument(2)) {
    523        var secondArgWhitelist = [
    524            /StringBuffer::ToString/,
    525            /AppendUTF\d+toUTF\d+/,
    526            /AppendASCIItoUTF\d+/,
    527        ];
    528        if (entry.matches(secondArgWhitelist))
    529            return true;
    530    }
    531 
    532    return false;
    533 }
    534 
    535 ///////////////////////////////////////////////////////////////////////////////
    536 // Sixgill Utilities
    537 ///////////////////////////////////////////////////////////////////////////////
    538 
    539 function variableName(variable)
    540 {
    541    return (variable && variable.Name) ? variable.Name[0] : null;
    542 }
    543 
    544 function stripFields(exp)
    545 {
    546    // Fields and index operations do not involve any dereferences. Remove them
    547    // from the expression but remember any encountered fields for use by
    548    // annotations later on.
    549    var fields = [];
    550    while (true) {
    551        if (exp.Kind == "Index") {
    552            exp = exp.Exp[0];
    553            continue;
    554        }
    555        if (exp.Kind == "Fld") {
    556            var csuName = exp.Field.FieldCSU.Type.Name;
    557            var fieldName = exp.Field.Name[0];
    558            assert(csuName && fieldName);
    559            fields.push(csuName + "." + fieldName);
    560            exp = exp.Exp[0];
    561            continue;
    562        }
    563        break;
    564    }
    565    return [exp, fields];
    566 }
    567 
    568 function isLocalVariable(variable)
    569 {
    570    switch (variable.Kind) {
    571      case "Return":
    572      case "Temp":
    573      case "Local":
    574      case "Arg":
    575        return true;
    576    }
    577    return false;
    578 }
    579 
    580 function isDirectCall(edge, regexp)
    581 {
    582    return edge.Kind == "Call"
    583        && edge.Exp[0].Kind == "Var"
    584        && regexp.test(variableName(edge.Exp[0].Variable));
    585 }
    586 
    587 function isZero(exp)
    588 {
    589    return exp.Kind == "Int" && exp.String == "0";
    590 }
    591 
    592 ///////////////////////////////////////////////////////////////////////////////
    593 // Analysis Structures
    594 ///////////////////////////////////////////////////////////////////////////////
    595 
    596 // Safe arguments are those which may be written through (directly, not through
    597 // pointer fields etc.) without concerns about thread safety. This includes
    598 // pointers to stack data, null pointers, and other data we know is thread
    599 // local, such as certain arguments to the root functions.
    600 //
    601 // Entries in the worklist keep track of the pointer arguments to the function
    602 // which are safe using a sorted array, so that this can be propagated down the
    603 // stack. Zero is |this|, and arguments are indexed starting at one.
    604 
    605 function WorklistEntry(name, safeArguments, stack, parameterNames)
    606 {
    607    this.name = name;
    608    this.safeArguments = safeArguments;
    609    this.stack = stack;
    610    this.parameterNames = parameterNames;
    611 }
    612 
    613 WorklistEntry.prototype.readable = function()
    614 {
    615    const [ mangled, readable ] = splitFunction(this.name);
    616    return readable;
    617 }
    618 
    619 WorklistEntry.prototype.mangledName = function()
    620 {
    621    var str = this.name;
    622    for (var safe of this.safeArguments)
    623        str += " SAFE " + safe;
    624    return str;
    625 }
    626 
    627 WorklistEntry.prototype.isSafeArgument = function(index)
    628 {
    629    for (var safe of this.safeArguments) {
    630        if (index == safe)
    631            return true;
    632    }
    633    return false;
    634 }
    635 
    636 WorklistEntry.prototype.setParameterName = function(index, name)
    637 {
    638    this.parameterNames[index] = name;
    639 }
    640 
    641 WorklistEntry.prototype.addSafeArgument = function(index)
    642 {
    643    if (this.isSafeArgument(index))
    644        return;
    645    this.safeArguments.push(index);
    646 
    647    // Sorting isn't necessary for correctness but makes printed stack info tidier.
    648    this.safeArguments.sort();
    649 }
    650 
    651 function safeArgumentIndex(variable)
    652 {
    653    if (variable.Kind == "This")
    654        return 0;
    655    if (variable.Kind == "Arg")
    656        return variable.Index + 1;
    657    return -1;
    658 }
    659 
    660 function nameMatches(name, match)
    661 {
    662    if (typeof match == "string") {
    663        if (name == match)
    664            return true;
    665    } else {
    666        assert(match instanceof RegExp);
    667        if (match.test(name))
    668            return true;
    669    }
    670    return false;
    671 }
    672 
    673 function nameMatchesArray(name, matchArray)
    674 {
    675    for (var match of matchArray) {
    676        if (nameMatches(name, match))
    677            return true;
    678    }
    679    return false;
    680 }
    681 
    682 WorklistEntry.prototype.matches = function(matchArray)
    683 {
    684    return nameMatchesArray(this.name, matchArray);
    685 }
    686 
    687 function CallSite(callee, safeArguments, location, parameterNames)
    688 {
    689    this.callee = callee;
    690    this.safeArguments = safeArguments;
    691    this.location = location;
    692    this.parameterNames = parameterNames;
    693 }
    694 
    695 CallSite.prototype.safeString = function()
    696 {
    697    if (this.safeArguments.length) {
    698        var str = "";
    699        for (var i = 0; i < this.safeArguments.length; i++) {
    700            var arg = this.safeArguments[i];
    701            if (arg in this.parameterNames)
    702                str += " " + this.parameterNames[arg];
    703            else
    704                str += " <" + ((arg == 0) ? "this" : "arg" + (arg - 1)) + ">";
    705        }
    706        return " ### SafeArguments:" + str;
    707    }
    708    return "";
    709 }
    710 
    711 ///////////////////////////////////////////////////////////////////////////////
    712 // Analysis Core
    713 ///////////////////////////////////////////////////////////////////////////////
    714 
    715 var errorCount = 0;
    716 var errorLimit = 100;
    717 
    718 // We want to suppress output for functions that ended up not having any
    719 // hazards, for brevity of the final output. So each new toplevel function will
    720 // initialize this to a string, which should be printed only if an error is
    721 // seen.
    722 var errorHeader;
    723 
    724 var startTime = new Date;
    725 function elapsedTime()
    726 {
    727    var seconds = (new Date - startTime) / 1000;
    728    return "[" + seconds.toFixed(2) + "s] ";
    729 }
    730 
    731 var options = parse_options([
    732    {
    733        name: '--strip-prefix',
    734        default: os.getenv('SOURCE') || '',
    735        type: 'string'
    736    },
    737    {
    738        name: '--add-prefix',
    739        default: os.getenv('URLPREFIX') || '',
    740        type: 'string'
    741    },
    742    {
    743        name: '--verbose',
    744        type: 'bool'
    745    },
    746 ]);
    747 
    748 function add_trailing_slash(str) {
    749    if (str == '')
    750        return str;
    751    return str.endsWith("/") ? str : str + "/";
    752 }
    753 
    754 var removePrefix = add_trailing_slash(options.strip_prefix);
    755 var addPrefix = add_trailing_slash(options.add_prefix);
    756 
    757 if (options.verbose) {
    758    printErr(`Removing prefix ${removePrefix} from paths`);
    759    printErr(`Prepending ${addPrefix} to paths`);
    760 }
    761 
    762 print(elapsedTime() + "Loading types...");
    763 if (os.getenv("TYPECACHE"))
    764    loadTypesWithCache('src_comp.xdb', os.getenv("TYPECACHE"));
    765 else
    766    loadTypes('src_comp.xdb');
    767 print(elapsedTime() + "Starting analysis...");
    768 
    769 var xdb = xdbLibrary();
    770 xdb.open("src_body.xdb");
    771 
    772 var minStream = xdb.min_data_stream();
    773 var maxStream = xdb.max_data_stream();
    774 var roots = [];
    775 
    776 var [flag, arg] = scriptArgs;
    777 if (flag && (flag == '-f' || flag == '--function')) {
    778    roots = [arg];
    779 } else {
    780    for (var bodyIndex = minStream; bodyIndex <= maxStream; bodyIndex++) {
    781        var key = xdb.read_key(bodyIndex);
    782        var name = key.readString();
    783        if (/^Gecko_/.test(name)) {
    784            var data = xdb.read_entry(key);
    785            if (/ServoBindings.cpp/.test(data.readString()))
    786                roots.push(name);
    787            xdb.free_string(data);
    788        }
    789        xdb.free_string(key);
    790    }
    791 }
    792 
    793 print(elapsedTime() + "Found " + roots.length + " roots.");
    794 for (var i = 0; i < roots.length; i++) {
    795    var root = roots[i];
    796    errorHeader = elapsedTime() + "#" + (i + 1) + " Analyzing " + root + " ...";
    797    try {
    798        processRoot(root);
    799    } catch (e) {
    800        if (e != "Error!")
    801            throw e;
    802    }
    803 }
    804 
    805 print(`${elapsedTime()}Completed analysis, found ${errorCount}/${errorLimit} allowed errors`);
    806 
    807 var currentBody;
    808 
    809 // All local variable assignments we have seen in either the outer or inner
    810 // function. This crosses loop boundaries, and currently has an unsoundness
    811 // where later assignments in a loop are not taken into account.
    812 var assignments;
    813 
    814 // All loops in the current function which are reachable off main thread.
    815 var reachableLoops;
    816 
    817 // Functions that are reachable from the current root.
    818 var reachable = {};
    819 
    820 function dumpError(entry, location, text)
    821 {
    822    if (errorHeader) {
    823        print(errorHeader);
    824        errorHeader = undefined;
    825    }
    826 
    827    var stack = entry.stack;
    828    print("Error: " + text);
    829    print("Location: " + entry.name + (location ? " @ " + location : "") + stack[0].safeString());
    830    print("Stack Trace:");
    831    // Include the callers in the stack trace instead of the callees. Make sure
    832    // the dummy stack entry we added for the original roots is in place.
    833    assert(stack[stack.length - 1].location == null);
    834    for (var i = 0; i < stack.length - 1; i++)
    835        print(stack[i + 1].callee + " @ " + stack[i].location + stack[i + 1].safeString());
    836    print("\n");
    837 
    838    if (++errorCount == errorLimit) {
    839        print("Maximum number of errors encountered, exiting...");
    840        quit();
    841    }
    842 
    843    throw "Error!";
    844 }
    845 
    846 // If edge is an assignment from a local variable, return the rhs variable.
    847 function variableAssignRhs(edge)
    848 {
    849    if (edge.Kind == "Assign" && edge.Exp[1].Kind == "Drf" && edge.Exp[1].Exp[0].Kind == "Var") {
    850        var variable = edge.Exp[1].Exp[0].Variable;
    851        if (isLocalVariable(variable))
    852            return variable;
    853    }
    854    return null;
    855 }
    856 
    857 function processAssign(body, entry, location, lhs, edge)
    858 {
    859    var fields;
    860    [lhs, fields] = stripFields(lhs);
    861 
    862    switch (lhs.Kind) {
    863      case "Var":
    864        var name = variableName(lhs.Variable);
    865        if (isLocalVariable(lhs.Variable)) {
    866            // Remember any assignments to local variables in this function.
    867            // Note that we ignore any points where the variable's address is
    868            // taken and indirect assignments might occur. This is an
    869            // unsoundness in the analysis.
    870 
    871            let assign = [body, edge];
    872 
    873            // Chain assignments if the RHS has only been assigned once.
    874            var rhsVariable = variableAssignRhs(edge);
    875            if (rhsVariable) {
    876                var rhsAssign = singleAssignment(variableName(rhsVariable));
    877                if (rhsAssign)
    878                    assign = rhsAssign;
    879            }
    880 
    881            if (!(name in assignments))
    882                assignments[name] = [];
    883            assignments[name].push(assign);
    884        } else {
    885            checkVariableAssignment(entry, location, name);
    886        }
    887        return;
    888      case "Drf":
    889        var variable = null;
    890        if (lhs.Exp[0].Kind == "Var") {
    891            variable = lhs.Exp[0].Variable;
    892            if (isSafeVariable(entry, variable))
    893                return;
    894        } else if (lhs.Exp[0].Kind == "Fld") {
    895            const {
    896                Name: [ fieldName ],
    897                Type: {Kind, Type: fieldType},
    898                FieldCSU: {Type: {Kind: containerTypeKind,
    899                                  Name: containerTypeName}}
    900            } = lhs.Exp[0].Field;
    901            const [containerExpr] = lhs.Exp[0].Exp;
    902 
    903            if (containerTypeKind == 'CSU' &&
    904                Kind == 'Pointer' &&
    905                isEdgeSafeArgument(entry, containerExpr) &&
    906                isSafeMemberPointer(containerTypeName, fieldName, fieldType))
    907            {
    908                return;
    909            }
    910        }
    911        if (fields.length)
    912            checkFieldWrite(entry, location, fields);
    913        else
    914            checkDereferenceWrite(entry, location, variableName(variable));
    915        return;
    916      case "Int":
    917        if (isZero(lhs)) {
    918            // This shows up under MOZ_ASSERT, to crash the process.
    919            return;
    920        }
    921    }
    922    dumpError(entry, location, "Unknown assignment " + JSON.stringify(lhs));
    923 }
    924 
    925 function get_location(rawLocation) {
    926    const filename = rawLocation.CacheString.replace(removePrefix, '');
    927    return addPrefix + filename + "#" + rawLocation.Line;
    928 }
    929 
    930 function process(entry, body, addCallee)
    931 {
    932    if (!("PEdge" in body))
    933        return;
    934 
    935    // Add any arguments which are safe due to annotations.
    936    if ("DefineVariable" in body) {
    937        for (var defvar of body.DefineVariable) {
    938            var index = safeArgumentIndex(defvar.Variable);
    939            if (index >= 0) {
    940                var varName = index ? variableName(defvar.Variable) : "this";
    941                assert(varName);
    942                entry.setParameterName(index, varName);
    943                var csuName = null;
    944                var type = defvar.Type;
    945                if (type.Kind == "Pointer" && type.Type.Kind == "CSU")
    946                    csuName = type.Type.Name;
    947                if (treatAsSafeArgument(entry, varName, csuName))
    948                    entry.addSafeArgument(index);
    949            }
    950        }
    951    }
    952 
    953    // Points in the body which are reachable if we are not on the main thread.
    954    var nonMainThreadPoints = [];
    955    nonMainThreadPoints[body.Index[0]] = true;
    956 
    957    for (var edge of body.PEdge) {
    958        // Ignore code that only executes on the main thread.
    959        if (!(edge.Index[0] in nonMainThreadPoints))
    960            continue;
    961 
    962        var location = get_location(body.PPoint[edge.Index[0] - 1].Location);
    963 
    964        var callees = getCallees(edge);
    965        for (var callee of callees) {
    966            switch (callee.kind) {
    967            case "direct":
    968                var safeArguments = getEdgeSafeArguments(entry, edge, callee.name);
    969                addCallee(new CallSite(callee.name, safeArguments, location, {}));
    970                break;
    971              case "resolved-field":
    972                break;
    973              case "field":
    974                var field = callee.csu + "." + callee.field;
    975                if (callee.isVirtual)
    976                    checkOverridableVirtualCall(entry, location, field);
    977                else
    978                    checkIndirectCall(entry, location, field);
    979                break;
    980              case "indirect":
    981                checkIndirectCall(entry, location, callee.variable);
    982                break;
    983              default:
    984                dumpError(entry, location, "Unknown call " + callee.kind);
    985                break;
    986            }
    987        }
    988 
    989        var fallthrough = true;
    990 
    991        if (edge.Kind == "Assign") {
    992            assert(edge.Exp.length == 2);
    993            processAssign(body, entry, location, edge.Exp[0], edge);
    994        } else if (edge.Kind == "Call") {
    995            assert(edge.Exp.length <= 2);
    996            if (edge.Exp.length == 2)
    997                processAssign(body, entry, location, edge.Exp[1], edge);
    998 
    999            // Treat assertion failures as if they don't return, so that
   1000            // asserting NS_IsMainThread() is sufficient to prevent the
   1001            // analysis from considering a block of code.
   1002            if (isDirectCall(edge, /MOZ_ReportAssertionFailure/))
   1003                fallthrough = false;
   1004        } else if (edge.Kind == "Loop") {
   1005            reachableLoops[edge.BlockId.Loop] = true;
   1006        } else if (edge.Kind == "Assume") {
   1007            if (testFailsOffMainThread(edge.Exp[0], edge.PEdgeAssumeNonZero))
   1008                fallthrough = false;
   1009        }
   1010 
   1011        if (fallthrough)
   1012            nonMainThreadPoints[edge.Index[1]] = true;
   1013    }
   1014 }
   1015 
   1016 function maybeProcessMissingFunction(entry, addCallee)
   1017 {
   1018    // If a function is missing it might be because a destructor Foo::~Foo() is
   1019    // being called but GCC only gave us an implementation for
   1020    // Foo::~Foo(int32). See computeCallgraph.js for a little more info.
   1021    var name = entry.name;
   1022    if (name.indexOf("::~") > 0 && name.indexOf("()") > 0) {
   1023        var callee = name.replace("()", "(int32)");
   1024        addCallee(new CallSite(name, entry.safeArguments, entry.stack[0].location, entry.parameterNames));
   1025        return true;
   1026    }
   1027 
   1028    // Similarly, a call to a C1 constructor might invoke the C4 constructor. A
   1029    // mangled constructor will be something like _ZN<length><name>C1E... or in
   1030    // the case of a templatized constructor, _ZN<length><name>C1I...EE... so
   1031    // we hack it and look for "C1E" or "C1I" and replace them with their C4
   1032    // variants. This will have rare false matches, but so far we haven't hit
   1033    // any external function calls of that sort.
   1034    if (entry.mangledName().includes("C1E") || entry.mangledName().includes("C1I")) {
   1035        var callee = name.replace("C1E", "C4E").replace("C1I", "C4I");
   1036        addCallee(new CallSite(name, entry.safeArguments, entry.stack[0].location, entry.parameterNames));
   1037        return true;
   1038    }
   1039 
   1040    // Hack to manually follow some typedefs that show up on some functions.
   1041    // This is a bug in the sixgill GCC plugin I think, since sixgill is
   1042    // supposed to follow any typedefs itself.
   1043    if (/mozilla::dom::Element/.test(name)) {
   1044        var callee = name.replace("mozilla::dom::Element", "Document::Element");
   1045        addCallee(new CallSite(name, entry.safeArguments, entry.stack[0].location, entry.parameterNames));
   1046        return true;
   1047    }
   1048 
   1049    // Hack for contravariant return types. When overriding a virtual method
   1050    // with a method that returns a different return type (a subtype of the
   1051    // original return type), we are getting the right mangled name but the
   1052    // wrong return type in the unmangled name.
   1053    if (/\$nsTextFrame*/.test(name)) {
   1054        var callee = name.replace("nsTextFrame", "nsIFrame");
   1055        addCallee(new CallSite(name, entry.safeArguments, entry.stack[0].location, entry.parameterNames));
   1056        return true;
   1057    }
   1058 
   1059    return false;
   1060 }
   1061 
   1062 function processRoot(name)
   1063 {
   1064    var safeArguments = [];
   1065    var parameterNames = {};
   1066    var worklist = [new WorklistEntry(name, safeArguments, [new CallSite(name, safeArguments, null, parameterNames)], parameterNames)];
   1067 
   1068    reachable = {};
   1069 
   1070    while (worklist.length > 0) {
   1071        var entry = worklist.pop();
   1072 
   1073        // In principle we would be better off doing a meet-over-paths here to get
   1074        // the common subset of arguments which are safe to write through. However,
   1075        // analyzing functions separately for each subset if simpler, ensures that
   1076        // the stack traces we produce accurately characterize the stack arguments,
   1077        // and should be fast enough for now.
   1078 
   1079        if (entry.mangledName() in reachable)
   1080            continue;
   1081        reachable[entry.mangledName()] = true;
   1082 
   1083        if (ignoreContents(entry))
   1084            continue;
   1085 
   1086        var data = xdb.read_entry(entry.name);
   1087        var dataString = data.readString();
   1088        var callees = [];
   1089        if (dataString.length) {
   1090            // Reverse the order of the bodies we process so that we visit the
   1091            // outer function and see its assignments before the inner loops.
   1092            assignments = {};
   1093            reachableLoops = {};
   1094            var bodies = JSON.parse(dataString).reverse();
   1095            for (var body of bodies) {
   1096                if (!body.BlockId.Loop || body.BlockId.Loop in reachableLoops) {
   1097                    currentBody = body;
   1098                    process(entry, body, Array.prototype.push.bind(callees));
   1099                }
   1100            }
   1101        } else {
   1102            if (!maybeProcessMissingFunction(entry, Array.prototype.push.bind(callees)))
   1103                checkExternalFunction(entry);
   1104        }
   1105        xdb.free_string(data);
   1106 
   1107        for (var callee of callees) {
   1108            if (!ignoreCallEdge(entry, callee.callee)) {
   1109                var nstack = [callee, ...entry.stack];
   1110                worklist.push(new WorklistEntry(callee.callee, callee.safeArguments, nstack, callee.parameterNames));
   1111            }
   1112        }
   1113    }
   1114 }
   1115 
   1116 function isEdgeSafeArgument(entry, exp)
   1117 {
   1118    var fields;
   1119    [exp, fields] = stripFields(exp);
   1120 
   1121    if (exp.Kind == "Var" && isLocalVariable(exp.Variable))
   1122        return true;
   1123    if (exp.Kind == "Drf" && exp.Exp[0].Kind == "Var") {
   1124        var variable = exp.Exp[0].Variable;
   1125        return isSafeVariable(entry, variable);
   1126    }
   1127    if (isZero(exp))
   1128        return true;
   1129    return false;
   1130 }
   1131 
   1132 function getEdgeSafeArguments(entry, edge, callee)
   1133 {
   1134    assert(edge.Kind == "Call");
   1135    var res = [];
   1136    if ("PEdgeCallInstance" in edge) {
   1137        if (isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
   1138            res.push(0);
   1139    }
   1140    if ("PEdgeCallArguments" in edge) {
   1141        var args = edge.PEdgeCallArguments.Exp;
   1142        for (var i = 0; i < args.length; i++) {
   1143            if (isEdgeSafeArgument(entry, args[i]))
   1144                res.push(i + 1);
   1145        }
   1146    }
   1147    return res;
   1148 }
   1149 
   1150 function singleAssignment(name)
   1151 {
   1152    if (name in assignments) {
   1153        var edges = assignments[name];
   1154        if (edges.length == 1)
   1155            return edges[0];
   1156    }
   1157    return null;
   1158 }
   1159 
   1160 function expressionValueEdge(exp) {
   1161    if (!(exp.Kind == "Var" && exp.Variable.Kind == "Temp"))
   1162        return null;
   1163    const assign = singleAssignment(variableName(exp.Variable));
   1164    if (!assign)
   1165        return null;
   1166    const [body, edge] = assign;
   1167    return edge;
   1168 }
   1169 
   1170 // Examples:
   1171 //
   1172 //   void foo(type* aSafe) {
   1173 //     type* safeBecauseNew = new type(...);
   1174 //     type* unsafeBecauseMultipleAssignments = new type(...);
   1175 //     if (rand())
   1176 //       unsafeBecauseMultipleAssignments = bar();
   1177 //     type* safeBecauseSingleAssignmentOfSafe = aSafe;
   1178 //   }
   1179 //
   1180 function isSafeVariable(entry, variable)
   1181 {
   1182    var index = safeArgumentIndex(variable);
   1183    if (index >= 0)
   1184        return entry.isSafeArgument(index);
   1185 
   1186    if (variable.Kind != "Temp" && variable.Kind != "Local")
   1187        return false;
   1188    var name = variableName(variable);
   1189 
   1190    if (!entry.safeLocals)
   1191        entry.safeLocals = new Map;
   1192    if (entry.safeLocals.has(name))
   1193        return entry.safeLocals.get(name);
   1194 
   1195    const safe = isSafeLocalVariable(entry, name);
   1196    entry.safeLocals.set(name, safe);
   1197    return safe;
   1198 }
   1199 
   1200 function isSafeLocalVariable(entry, name)
   1201 {
   1202    // If there is a single place where this variable has been assigned on
   1203    // edges we are considering, look at that edge.
   1204    var assign = singleAssignment(name);
   1205    if (assign) {
   1206        const [body, edge] = assign;
   1207 
   1208        // Treat temporary pointers to DebugOnly contents as thread local.
   1209        if (isDirectCall(edge, /DebugOnly.*?::operator/))
   1210            return true;
   1211 
   1212        // Treat heap allocated pointers as thread local during construction.
   1213        // Hopefully the construction code doesn't leak pointers to the object
   1214        // to places where other threads might access it.
   1215        if (isDirectCall(edge, /operator new/) ||
   1216            isDirectCall(edge, /nsCSSValue::Array::Create/))
   1217        {
   1218            return true;
   1219        }
   1220 
   1221        if ("PEdgeCallInstance" in edge) {
   1222            // References to the contents of an array are threadsafe if the array
   1223            // itself is threadsafe.
   1224            if ((isDirectCall(edge, /operator\[\]/) ||
   1225                 isDirectCall(edge, /nsTArray.*?::InsertElementAt\b/) ||
   1226                 isDirectCall(edge, /nsStyleContent::ContentAt/) ||
   1227                 isDirectCall(edge, /nsTArray_base.*?::GetAutoArrayBuffer\b/)) &&
   1228                isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
   1229            {
   1230                return true;
   1231            }
   1232 
   1233            // Watch for the coerced result of a getter_AddRefs or getter_Copies call.
   1234            if (isDirectCall(edge, /operator /)) {
   1235                var otherEdge = expressionValueEdge(edge.PEdgeCallInstance.Exp);
   1236                if (otherEdge &&
   1237                    isDirectCall(otherEdge, /getter_(?:AddRefs|Copies)/) &&
   1238                    isEdgeSafeArgument(entry, otherEdge.PEdgeCallArguments.Exp[0]))
   1239                {
   1240                    return true;
   1241                }
   1242            }
   1243 
   1244            // RefPtr::operator->() and operator* transmit the safety of the
   1245            // RefPtr to the return value.
   1246            if (isDirectCall(edge, /RefPtr<.*?>::operator(->|\*)\(\)/) &&
   1247                isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
   1248            {
   1249                return true;
   1250            }
   1251 
   1252            // Placement-new returns a pointer that is as safe as the pointer
   1253            // passed to it. Exp[0] is the size, Exp[1] is the pointer/address.
   1254            // Note that the invocation of the constructor is a separate call,
   1255            // and so need not be considered here.
   1256            if (isDirectCall(edge, /operator new/) &&
   1257                edge.PEdgeCallInstance.Exp.length == 2 &&
   1258                isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp[1]))
   1259            {
   1260                return true;
   1261            }
   1262 
   1263            // Coercion via AsAString preserves safety.
   1264            if (isDirectCall(edge, /AsAString/) &&
   1265                isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
   1266            {
   1267                return true;
   1268            }
   1269 
   1270            // Special case:
   1271            //
   1272            //   keyframe->mTimingFunction.emplace()
   1273            //   keyframe->mTimingFunction->Init()
   1274            //
   1275            // The object calling Init should be considered safe here because
   1276            // we just emplaced it, though in general keyframe::operator->
   1277            // could do something crazy.
   1278            if (isDirectCall(edge, /operator->/)) do {
   1279                const predges = getPredecessors(body)[edge.Index[0]];
   1280                if (!predges || predges.length != 1)
   1281                    break;
   1282                const predge = predges[0];
   1283                if (!isDirectCall(predge, /\bemplace\b/))
   1284                    break;
   1285                const instance = predge.PEdgeCallInstance;
   1286                if (JSON.stringify(instance) == JSON.stringify(edge.PEdgeCallInstance))
   1287                    return true;
   1288            } while (false);
   1289        }
   1290 
   1291        if (isSafeAssignment(entry, edge, name))
   1292            return true;
   1293 
   1294        // Watch out for variables which were assigned arguments.
   1295        var rhsVariable = variableAssignRhs(edge);
   1296        if (rhsVariable)
   1297            return isSafeVariable(entry, rhsVariable);
   1298    }
   1299 
   1300    // When temporary stack structures are created (either to return or to call
   1301    // methods on without assigning them a name), the generated sixgill JSON is
   1302    // rather strange. The temporary has structure type and is never assigned
   1303    // to, but is dereferenced. GCC is probably not showing us everything it is
   1304    // doing to compile this code. Pattern match for this case here.
   1305 
   1306    // The variable should have structure type.
   1307    var type = null;
   1308    for (var defvar of currentBody.DefineVariable) {
   1309        if (variableName(defvar.Variable) == name) {
   1310            type = defvar.Type;
   1311            break;
   1312        }
   1313    }
   1314    if (!type || type.Kind != "CSU")
   1315        return false;
   1316 
   1317    // The variable should not have been written to anywhere up to this point.
   1318    // If it is initialized at this point we should have seen *some* write
   1319    // already, since the CFG edges are visited in reverse post order.
   1320    if (name in assignments)
   1321        return false;
   1322 
   1323    return true;
   1324 }
   1325 
   1326 function isSafeMemberPointer(containerType, memberName, memberType)
   1327 {
   1328    // nsTArray owns its header.
   1329    if (containerType.includes("nsTArray_base") && memberName == "mHdr")
   1330        return true;
   1331 
   1332    if (memberType.Kind != 'Pointer')
   1333        return false;
   1334 
   1335    // Special-cases go here :)
   1336    return false;
   1337 }
   1338 
   1339 // Return whether 'exp == value' holds only when execution is on the main thread.
   1340 function testFailsOffMainThread(exp, value) {
   1341    switch (exp.Kind) {
   1342      case "Drf":
   1343        var edge = expressionValueEdge(exp.Exp[0]);
   1344        if (edge) {
   1345            if (isDirectCall(edge, /NS_IsMainThread/) && value)
   1346                return true;
   1347            if (isDirectCall(edge, /IsInServoTraversal/) && !value)
   1348                return true;
   1349            if (isDirectCall(edge, /IsCurrentThreadInServoTraversal/) && !value)
   1350                return true;
   1351            if (isDirectCall(edge, /__builtin_expect/))
   1352                return testFailsOffMainThread(edge.PEdgeCallArguments.Exp[0], value);
   1353            if (edge.Kind == "Assign")
   1354                return testFailsOffMainThread(edge.Exp[1], value);
   1355        }
   1356        break;
   1357      case "Unop":
   1358        if (exp.OpCode == "LogicalNot")
   1359            return testFailsOffMainThread(exp.Exp[0], !value);
   1360        break;
   1361      case "Binop":
   1362        if (exp.OpCode == "NotEqual" || exp.OpCode == "Equal") {
   1363            var cmpExp = isZero(exp.Exp[0])
   1364                ? exp.Exp[1]
   1365                : (isZero(exp.Exp[1]) ? exp.Exp[0] : null);
   1366            if (cmpExp)
   1367                return testFailsOffMainThread(cmpExp, exp.OpCode == "NotEqual" ? value : !value);
   1368        }
   1369        break;
   1370      case "Int":
   1371        if (exp.String == "0" && value)
   1372            return true;
   1373        if (exp.String == "1" && !value)
   1374            return true;
   1375        break;
   1376    }
   1377    return false;
   1378 }