tor-browser

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

annotations.js (19415B)


      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 // Ignore calls made through these function pointers
     10 var ignoreIndirectCalls = {
     11    "mallocSizeOf" : true,
     12    "aMallocSizeOf" : true,
     13    "__conv" : true,
     14    "__convf" : true,
     15    "callback_newtable": true,
     16    "gLogAddRefFunc": true,
     17    "gLogReleaseFunc": true,
     18 };
     19 
     20 // Types that when constructed with no arguments, are "safe" values (they do
     21 // not contain GC pointers, or values with nontrivial destructors.)
     22 var typesWithSafeConstructors = new Set([
     23    "mozilla::Maybe",
     24    "mozilla::dom::Nullable",
     25    "mozilla::dom::Optional",
     26    "mozilla::UniquePtr",
     27    "js::UniquePtr"
     28 ]);
     29 
     30 var resetterMethods = {
     31    'mozilla::Maybe': new Set(["reset"]),
     32    'mozilla::UniquePtr': new Set(["reset"]),
     33    'js::UniquePtr': new Set(["reset"]),
     34    'mozilla::dom::Nullable': new Set(["SetNull"]),
     35    'mozilla::dom::TypedArray_base': new Set(["Reset"]),
     36    'RefPtr': new Set(["forget"]),
     37    'nsCOMPtr': new Set(["forget"]),
     38    'JS::AutoAssertNoGC': new Set(["reset"]),
     39 };
     40 
     41 function isRefcountedDtor(name) {
     42    return name.includes("::~RefPtr(") || name.includes("::~nsCOMPtr(");
     43 }
     44 
     45 function indirectCallCannotGC(fullCaller, fullVariable)
     46 {
     47    var caller = readable(fullCaller);
     48 
     49    // This is usually a simple variable name, but sometimes a full name gets
     50    // passed through. And sometimes that name is truncated. Examples:
     51    //   _ZL13gAbortHandler$mozalloc_oom.cpp:void (* gAbortHandler)(size_t)
     52    //   _ZL14pMutexUnlockFn$umutex.cpp:void (* pMutexUnlockFn)(const void*
     53    var name = readable(fullVariable);
     54 
     55    if (name in ignoreIndirectCalls)
     56        return true;
     57 
     58    if (name == "mapper" && caller == "ptio.c:pt_MapError")
     59        return true;
     60 
     61    if (name == "params" && caller == "PR_ExplodeTime")
     62        return true;
     63 
     64    // hook called during script finalization which cannot GC.
     65    if (/CallDestroyScriptHook/.test(caller))
     66        return true;
     67 
     68    // Call through a 'callback' function pointer, in a place where we're going
     69    // to be throwing a JS exception.
     70    if (name == "callback" && caller.includes("js::ErrorToException"))
     71        return true;
     72 
     73    // The math cache only gets called with non-GC math functions.
     74    if (name == "f" && caller.includes("js::MathCache::lookup"))
     75        return true;
     76 
     77    // It would probably be better to somehow rewrite PR_CallOnce(foo) into a
     78    // call of foo, but for now just assume that nobody is crazy enough to use
     79    // PR_CallOnce with a function that can GC.
     80    if (name == "func" && caller == "PR_CallOnce")
     81        return true;
     82 
     83    return false;
     84 }
     85 
     86 // Ignore calls through functions pointers with these types
     87 var ignoreClasses = {
     88    "JSStringFinalizer" : true,
     89    "SprintfState" : true,
     90    "SprintfStateStr" : true,
     91    "JSLocaleCallbacks" : true,
     92    "JSC::ExecutableAllocator" : true,
     93    "PRIOMethods": true,
     94    "_MD_IOVector" : true,
     95    "malloc_table_t": true, // replace_malloc
     96    "malloc_hook_table_t": true, // replace_malloc
     97    "mozilla::MallocSizeOf": true,
     98    "MozMallocSizeOf": true,
     99 };
    100 
    101 // Ignore calls through TYPE.FIELD, where TYPE is the class or struct name containing
    102 // a function pointer field named FIELD.
    103 var ignoreCallees = {
    104    "js::Class.trace" : true,
    105    "js::Class.finalize" : true,
    106    "JSClassOps.trace" : true,
    107    "JSClassOps.finalize" : true,
    108    "JSRuntime.destroyPrincipals" : true,
    109    "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC
    110    "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC.
    111    "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
    112    "PLDHashTableOps.hashKey" : true,
    113    "PLDHashTableOps.clearEntry" : true,
    114    "z_stream_s.zfree" : true,
    115    "z_stream_s.zalloc" : true,
    116    "GrGLInterface.fCallback" : true,
    117    "std::strstreambuf._M_alloc_fun" : true,
    118    "std::strstreambuf._M_free_fun" : true,
    119    "struct js::gc::Callback<void (*)(JSContext*, void*)>.op" : true,
    120    "mozilla::ThreadSharedFloatArrayBufferList::Storage.mFree" : true,
    121    "mozilla::SizeOfState.mMallocSizeOf": true,
    122    "mozilla::gfx::SourceSurfaceRawData.mDeallocator": true,
    123 };
    124 
    125 function fieldCallCannotGC(csu, fullfield)
    126 {
    127    if (csu in ignoreClasses)
    128        return true;
    129    if (fullfield in ignoreCallees)
    130        return true;
    131 
    132    // Example: fmt::v11::detail::buffer<char16_t>.grow_
    133    if (/^fmt\b.*::buffer/.test(fullfield))
    134        return true;
    135    // Example: fmt::v11::detail::custom_value<fmt::v11::context>.format
    136    // Example: fmt::v11::detail::custom_value<fmt::v11::generic_context<fmt::v11::basic_appender<wchar_t>, wchar_t> >.format
    137    if (/^fmt\b.*::custom_value<.*>/.test(fullfield))
    138       return true;
    139 
    140    return false;
    141 }
    142 
    143 function ignoreEdgeUse(edge, variable, body)
    144 {
    145    // Horrible special case for ignoring a false positive in xptcstubs: there
    146    // is a local variable 'paramBuffer' holding an array of nsXPTCMiniVariant
    147    // on the stack, which appears to be live across a GC call because its
    148    // constructor is called when the array is initialized, even though the
    149    // constructor is a no-op. So we'll do a very narrow exclusion for the use
    150    // that incorrectly started the live range, which was basically "__temp_1 =
    151    // paramBuffer".
    152    //
    153    // By scoping it so narrowly, we can detect most hazards that would be
    154    // caused by modifications in the PrepareAndDispatch code. It just barely
    155    // avoids having a hazard already.
    156    if (('Name' in variable) && (variable.Name[0] == 'paramBuffer')) {
    157        if (body.BlockId.Kind == 'Function' && body.BlockId.Variable.Name[0] == 'PrepareAndDispatch')
    158            if (edge.Kind == 'Assign' && edge.Type.Kind == 'Pointer')
    159                if (edge.Exp[0].Kind == 'Var' && edge.Exp[1].Kind == 'Var')
    160                    if (edge.Exp[1].Variable.Kind == 'Local' && edge.Exp[1].Variable.Name[0] == 'paramBuffer')
    161                        return true;
    162    }
    163 
    164    // Functions which should not be treated as using variable.
    165    if (edge.Kind == "Call") {
    166        var callee = edge.Exp[0];
    167        if (callee.Kind == "Var") {
    168            var name = callee.Variable.Name[0];
    169            if (/~DebugOnly/.test(name))
    170                return true;
    171            if (/~ScopedThreadSafeStringInspector/.test(name))
    172                return true;
    173        }
    174    }
    175 
    176    return false;
    177 }
    178 
    179 function ignoreEdgeAddressTaken(edge)
    180 {
    181    // Functions which may take indirect pointers to unrooted GC things,
    182    // but will copy them into rooted locations before calling anything
    183    // that can GC. These parameters should usually be replaced with
    184    // handles or mutable handles.
    185    if (edge.Kind == "Call") {
    186        var callee = edge.Exp[0];
    187        if (callee.Kind == "Var") {
    188            var name = callee.Variable.Name[0];
    189            if (/js::Invoke\(/.test(name))
    190                return true;
    191        }
    192    }
    193 
    194    return false;
    195 }
    196 
    197 // Ignore calls of these functions (so ignore any stack containing these)
    198 var ignoreFunctions = {
    199    "ptio.c:pt_MapError" : true,
    200    "je_malloc_printf" : true,
    201    "malloc_usable_size" : true,
    202    "vprintf_stderr" : true,
    203    "PR_ExplodeTime" : true,
    204    "PR_ErrorInstallTable" : true,
    205    "PR_SetThreadPrivate" : true,
    206    "uint8 NS_IsMainThread()" : true,
    207 
    208    // Has an indirect call under it by the name "__f", which seemed too
    209    // generic to ignore by itself.
    210    "void* std::_Locale_impl::~_Locale_impl(int32)" : true,
    211 
    212    // Bug 1056410 - devirtualization prevents the standard nsISupports::Release heuristic from working
    213    "uint32 nsXPConnect::Release()" : true,
    214    "uint32 nsAtom::Release()" : true,
    215 
    216    // Allocation API
    217    "malloc": true,
    218    "calloc": true,
    219    "realloc": true,
    220    "free": true,
    221 
    222    // mozjemalloc can run callbacks through this function.  They should be
    223    // used with care and shouldn't GC.
    224    "void arena_t::MayDoOrQueuePurge(int32, int8*)": true,
    225 
    226    // FIXME!
    227    "NS_LogInit": true,
    228    "NS_LogTerm": true,
    229    "NS_LogAddRef": true,
    230    "NS_LogRelease": true,
    231    "NS_LogCtor": true,
    232    "NS_LogDtor": true,
    233    "NS_LogCOMPtrAddRef": true,
    234    "NS_LogCOMPtrRelease": true,
    235 
    236    // FIXME!
    237    "NS_DebugBreak": true,
    238 
    239    // Similar to heap snapshot mock classes, and GTests below. This posts a
    240    // synchronous runnable when a GTest fails, and we are pretty sure that the
    241    // particular runnable it posts can't even GC, but the analysis isn't
    242    // currently smart enough to determine that. In either case, this is (a)
    243    // only in GTests, and (b) only when the Gtest has already failed. We have
    244    // static and dynamic checks for no GC in the non-test code, and in the test
    245    // code we fall back to only the dynamic checks.
    246    "void test::RingbufferDumper::OnTestPartResult(testing::TestPartResult*)" : true,
    247 
    248    "float64 JS_GetCurrentEmbedderTime()" : true,
    249 
    250    // This calls any JSObjectMovedOp for the tenured object via an indirect call.
    251    "JSObject* js::TenuringTracer::moveToTenuredSlow(JSObject*)" : true,
    252 
    253    "void js::Nursery::freeMallocedBuffers()" : true,
    254 
    255    "void js::AutoEnterOOMUnsafeRegion::crash(uint64, int8*)" : true,
    256    "void js::AutoEnterOOMUnsafeRegion::crash_impl(uint64, int8*)" : true,
    257 
    258    "void mozilla::dom::WorkerPrivate::AssertIsOnWorkerThread() const" : true,
    259 
    260    // It would be cool to somehow annotate that nsTHashtable<T> will use
    261    // nsTHashtable<T>::s_MatchEntry for its matchEntry function pointer, but
    262    // there is no mechanism for that. So we will just annotate a particularly
    263    // troublesome logging-related usage.
    264    "EntryType* nsTHashtable<EntryType>::PutEntry(nsTHashtable<EntryType>::KeyType, const fallible_t&) [with EntryType = nsBaseHashtableET<nsCharPtrHashKey, nsAutoPtr<mozilla::LogModule> >; nsTHashtable<EntryType>::KeyType = const char*; nsTHashtable<EntryType>::fallible_t = mozilla::fallible_t]" : true,
    265    "EntryType* nsTHashtable<EntryType>::GetEntry(nsTHashtable<EntryType>::KeyType) const [with EntryType = nsBaseHashtableET<nsCharPtrHashKey, nsAutoPtr<mozilla::LogModule> >; nsTHashtable<EntryType>::KeyType = const char*]" : true,
    266    "EntryType* nsTHashtable<EntryType>::PutEntry(nsTHashtable<EntryType>::KeyType) [with EntryType = nsBaseHashtableET<nsPtrHashKey<const mozilla::BlockingResourceBase>, nsAutoPtr<mozilla::DeadlockDetector<mozilla::BlockingResourceBase>::OrderingEntry> >; nsTHashtable<EntryType>::KeyType = const mozilla::BlockingResourceBase*]" : true,
    267    "EntryType* nsTHashtable<EntryType>::GetEntry(nsTHashtable<EntryType>::KeyType) const [with EntryType = nsBaseHashtableET<nsPtrHashKey<const mozilla::BlockingResourceBase>, nsAutoPtr<mozilla::DeadlockDetector<mozilla::BlockingResourceBase>::OrderingEntry> >; nsTHashtable<EntryType>::KeyType = const mozilla::BlockingResourceBase*]" : true,
    268 
    269    // VTune internals that lazy-load a shared library and make IndirectCalls.
    270    "iJIT_IsProfilingActive" : true,
    271    "iJIT_NotifyEvent": true,
    272 
    273    // The big hammers.
    274    "PR_GetCurrentThread" : true,
    275    "calloc" : true,
    276 
    277    // This will happen early enough in initialization to not matter.
    278    "_PR_UnixInit" : true,
    279 
    280    "uint8 nsContentUtils::IsExpandedPrincipal(nsIPrincipal*)" : true,
    281 
    282    "void mozilla::AutoProfilerLabel::~AutoProfilerLabel(int32)" : true,
    283 
    284    // Stores a function pointer in an AutoProfilerLabelData struct and calls it.
    285    // And it's in mozglue, which doesn't have access to the attributes yet.
    286    "void mozilla::ProfilerLabelEnd(std::tuple<void*, unsigned int>*)" : true,
    287 
    288    // This gets into PLDHashTable function pointer territory, and should get
    289    // set up early enough to not do anything when it matters anyway.
    290    "mozilla::LogModule* mozilla::LogModule::Get(int8*)": true,
    291 
    292    // This annotation is correct, but the reasoning is still being hashed out
    293    // in bug 1582326 comment 8 and on.
    294    "nsCycleCollector.cpp:nsISupports* CanonicalizeXPCOMParticipant(nsISupports*)": true,
    295 
    296    // PLDHashTable again
    297    "void mozilla::DeadlockDetector<T>::Add(const T*) [with T = mozilla::BlockingResourceBase]": true,
    298 
    299    // OOM handling during logging
    300    "void mozilla::detail::log_print(mozilla::LogModule*, int32, int8*)": true,
    301 
    302    // This would need to know that the nsCOMPtr refcount will not go to zero.
    303    "uint8 XPCJSRuntime::DescribeCustomObjects(JSObject*, JSClass*, int8[72]*)[72]) const": true,
    304 
    305    // As the comment says "Refcount isn't zero, so Suspect won't delete anything."
    306    "uint64 nsCycleCollectingAutoRefCnt::incr(void*, nsCycleCollectionParticipant*) [with void (* suspect)(void*, nsCycleCollectionParticipant*, nsCycleCollectingAutoRefCnt*, bool*) = NS_CycleCollectorSuspect3; uintptr_t = long unsigned int]": true,
    307 
    308    // Calls MergeSort
    309    "uint8 v8::internal::RegExpDisjunction::SortConsecutiveAtoms(v8::internal::RegExpCompiler*)": true,
    310 
    311    // nsIEventTarget.IsOnCurrentThreadInfallible does not get resolved, and
    312    // this is called on non-JS threads so cannot use AutoSuppressGCAnalysis.
    313    "uint8 nsAutoOwningEventTarget::IsCurrentThread() const": true,
    314 
    315    // ~JSStreamConsumer calls 2 ~RefCnt/~nsCOMPtr destructors for its fields,
    316    // but the body of the destructor is written so that all Releases
    317    // are proxied, and the members will all be empty at destruction time.
    318    "void mozilla::dom::JSStreamConsumer::~JSStreamConsumer() [[base_dtor]]": true,
    319 };
    320 
    321 function extraGCFunctions(readableNames) {
    322    return ["ffi_call"].filter(f => f in readableNames);
    323 }
    324 
    325 function isProtobuf(name)
    326 {
    327    return name.match(/\bgoogle::protobuf\b/) ||
    328           name.match(/\bmozilla::devtools::protobuf\b/);
    329 }
    330 
    331 function isHeapSnapshotMockClass(name)
    332 {
    333    return name.match(/\bMockWriter\b/) ||
    334           name.match(/\bMockDeserializedNode\b/);
    335 }
    336 
    337 function isGTest(name)
    338 {
    339    return name.match(/\btesting::/);
    340 }
    341 
    342 function isICU(name)
    343 {
    344    return name.match(/\bicu_\d+::/) ||
    345           name.match(/u(prv_malloc|prv_realloc|prv_free|case_toFullLower)_\d+/)
    346 }
    347 
    348 function ignoreGCFunction(mangled, readableNames)
    349 {
    350    // Field calls will not be in readableNames
    351    if (!(mangled in readableNames))
    352        return false;
    353 
    354    const fun = readableNames[mangled][0];
    355 
    356    if (fun in ignoreFunctions)
    357        return true;
    358 
    359    // The protobuf library, and [de]serialization code generated by the
    360    // protobuf compiler, uses a _ton_ of function pointers but they are all
    361    // internal. The same is true for ICU. Easiest to just ignore that mess
    362    // here.
    363    if (isProtobuf(fun) || isICU(fun))
    364        return true;
    365 
    366    // Ignore anything that goes through heap snapshot GTests or mocked classes
    367    // used in heap snapshot GTests. GTest and GMock expose a lot of virtual
    368    // methods and function pointers that could potentially GC after an
    369    // assertion has already failed (depending on user-provided code), but don't
    370    // exhibit that behavior currently. For non-test code, we have dynamic and
    371    // static checks that ensure we don't GC. However, for test code we opt out
    372    // of static checks here, because of the above stated GMock/GTest issues,
    373    // and rely on only the dynamic checks provided by AutoAssertCannotGC.
    374    if (isHeapSnapshotMockClass(fun) || isGTest(fun))
    375        return true;
    376 
    377    // Templatized function
    378    if (fun.includes("void nsCOMPtr<T>::Assert_NoQueryNeeded()"))
    379        return true;
    380 
    381    // Bug 1577915 - Sixgill is ignoring a template param that makes its CFG
    382    // impossible.
    383    if (fun.includes("UnwrapObjectInternal") && fun.includes("mayBeWrapper = false"))
    384        return true;
    385 
    386    // These call through an 'op' function pointer.
    387    if (fun.includes("js::WeakMap<Key, Value, HashPolicy>::getDelegate("))
    388        return true;
    389 
    390    // TODO: modify refillFreeList<NoGC> to not need data flow analysis to
    391    // understand it cannot GC. As of gcc 6, the same problem occurs with
    392    // tryNewTenuredThing, tryNewNurseryObject, and others.
    393    if (/refillFreeList|tryNew/.test(fun) && /= js::NoGC/.test(fun))
    394        return true;
    395 
    396    return false;
    397 }
    398 
    399 function stripUCSAndNamespace(name)
    400 {
    401    name = name.replace(/(struct|class|union|const) /g, "");
    402    name = name.replace(/(js::ctypes::|js::|JS::|mozilla::dom::|mozilla::)/g, "");
    403    return name;
    404 }
    405 
    406 function extraRootedGCThings()
    407 {
    408    return [ 'JSAddonId' ];
    409 }
    410 
    411 function extraRootedPointers()
    412 {
    413    return [
    414    ];
    415 }
    416 
    417 function isRootedGCPointerTypeName(name)
    418 {
    419    name = stripUCSAndNamespace(name);
    420 
    421    if (name.startsWith('MaybeRooted<'))
    422        return /\(js::AllowGC\)1u>::RootType/.test(name);
    423 
    424    return false;
    425 }
    426 
    427 function isUnsafeStorage(typeName)
    428 {
    429    typeName = stripUCSAndNamespace(typeName);
    430    return typeName.startsWith('UniquePtr<');
    431 }
    432 
    433 // If edgeType is a constructor type, return whatever bits it implies for its
    434 // scope (or zero if not matching).
    435 function isLimitConstructor(typeInfo, edgeType, varName)
    436 {
    437    // Check whether this could be a constructor
    438    if (edgeType.Kind != 'Function')
    439        return 0;
    440    if (!('TypeFunctionCSU' in edgeType))
    441        return 0;
    442    if (edgeType.Type.Kind != 'Void')
    443        return 0;
    444 
    445    // Check whether the type is a known suppression type.
    446    var type = edgeType.TypeFunctionCSU.Type.Name;
    447    let attrs = 0;
    448    if (type in typeInfo.GCSuppressors)
    449        attrs = attrs | ATTR_GC_SUPPRESSED;
    450 
    451    // And now make sure this is the constructor, not some other method on a
    452    // suppression type. varName[0] contains the qualified name.
    453    var [ mangled, unmangled ] = splitFunction(varName[0]);
    454    if (mangled.search(/C\d[EI]/) == -1)
    455        return 0; // Mangled names of constructors have C<num>E or C<num>I
    456    var m = unmangled.match(/([~\w]+)(?:<.*>)?\(/);
    457    if (!m)
    458        return 0;
    459    var type_stem = type.replace(/\w+::/g, '').replace(/\<.*\>/g, '');
    460    if (m[1] != type_stem)
    461        return 0;
    462 
    463    return attrs;
    464 }
    465 
    466 // XPIDL-generated methods may invoke JS code, depending on the IDL
    467 // attributes. This is not visible in the static callgraph since it
    468 // goes through generated asm code. We can use the JS_HAZ_CAN_RUN_SCRIPT
    469 // annotation to tell whether this is possible, which is set programmatically
    470 // by the code generator when needed (bug 1347999):
    471 // https://searchfox.org/mozilla-central/rev/81c52abeec336685330af5956c37b4bcf8926476/xpcom/idl-parser/xpidl/header.py#213-219
    472 //
    473 // Note that WebIDL callbacks can also invoke JS code, but our code generator
    474 // produces regular C++ code and so does not need any annotations. (There will
    475 // be a call to JS::Call() or similar.)
    476 function virtualCanRunJS(csu, field)
    477 {
    478    const tags = typeInfo.OtherFieldTags;
    479    const iface = tags[csu]
    480    if (!iface) {
    481        return false;
    482    }
    483    const virtual_method_tags = iface[field];
    484    return virtual_method_tags && virtual_method_tags.includes("Can run script");
    485 }
    486 
    487 function listNonGCPointers() {
    488    return [
    489        // Safe only because jsids are currently only made from pinned strings.
    490        'NPIdentifier',
    491    ];
    492 }
    493 
    494 function isJSNative(mangled)
    495 {
    496    // _Z...E = function
    497    // 9JSContext = JSContext*
    498    // j = uint32
    499    // PN2JS5Value = JS::Value*
    500    //   P = pointer
    501    //   N2JS = JS::
    502    //   5Value = Value
    503    return mangled.endsWith("P9JSContextjPN2JS5ValueE") && mangled.startsWith("_Z");
    504 }