| /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ |
| |
| "use strict"; |
| |
| // RAII types within which we should assume GC is suppressed, eg |
| // AutoSuppressGC. |
| var GCSuppressionTypes = []; |
| |
| // Ignore calls made through these function pointers |
| var ignoreIndirectCalls = { |
| "mallocSizeOf" : true, |
| "aMallocSizeOf" : true, |
| "_malloc_message" : true, |
| "je_malloc_message" : true, |
| "chunk_dalloc" : true, |
| "chunk_alloc" : true, |
| "__conv" : true, |
| "__convf" : true, |
| "prerrortable.c:callback_newtable" : true, |
| "mozalloc_oom.cpp:void (* gAbortHandler)(size_t)" : true, |
| }; |
| |
| function indirectCallCannotGC(fullCaller, fullVariable) |
| { |
| var caller = readable(fullCaller); |
| |
| // This is usually a simple variable name, but sometimes a full name gets |
| // passed through. And sometimes that name is truncated. Examples: |
| // _ZL13gAbortHandler|mozalloc_oom.cpp:void (* gAbortHandler)(size_t) |
| // _ZL14pMutexUnlockFn|umutex.cpp:void (* pMutexUnlockFn)(const void* |
| var name = readable(fullVariable); |
| |
| if (name in ignoreIndirectCalls) |
| return true; |
| |
| if (name == "mapper" && caller == "ptio.c:pt_MapError") |
| return true; |
| |
| if (name == "params" && caller == "PR_ExplodeTime") |
| return true; |
| |
| if (name == "op" && /GetWeakmapKeyDelegate/.test(caller)) |
| return true; |
| |
| // hook called during script finalization which cannot GC. |
| if (/CallDestroyScriptHook/.test(caller)) |
| return true; |
| |
| // template method called during marking and hence cannot GC |
| if (name == "op" && caller.indexOf("bool js::WeakMap<Key, Value, HashPolicy>::keyNeedsMark(JSObject*)") != -1) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Ignore calls through functions pointers with these types |
| var ignoreClasses = { |
| "JSStringFinalizer" : true, |
| "SprintfState" : true, |
| "SprintfStateStr" : true, |
| "JSLocaleCallbacks" : true, |
| "JSC::ExecutableAllocator" : true, |
| "PRIOMethods": true, |
| "XPCOMFunctions" : true, // I'm a little unsure of this one |
| "_MD_IOVector" : true, |
| "malloc_table_t": true, // replace_malloc |
| "malloc_hook_table_t": true, // replace_malloc |
| }; |
| |
| // Ignore calls through TYPE.FIELD, where TYPE is the class or struct name containing |
| // a function pointer field named FIELD. |
| var ignoreCallees = { |
| "js::Class.trace" : true, |
| "js::Class.finalize" : true, |
| "JSRuntime.destroyPrincipals" : true, |
| "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC |
| "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC. |
| "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC. |
| "PLDHashTableOps.hashKey" : true, |
| "z_stream_s.zfree" : true, |
| "z_stream_s.zalloc" : true, |
| "GrGLInterface.fCallback" : true, |
| "std::strstreambuf._M_alloc_fun" : true, |
| "std::strstreambuf._M_free_fun" : true, |
| "struct js::gc::Callback<void (*)(JSRuntime*, void*)>.op" : true, |
| }; |
| |
| function fieldCallCannotGC(csu, fullfield) |
| { |
| if (csu in ignoreClasses) |
| return true; |
| if (fullfield in ignoreCallees) |
| return true; |
| return false; |
| } |
| |
| function ignoreEdgeUse(edge, variable, body) |
| { |
| // Horrible special case for ignoring a false positive in xptcstubs: there |
| // is a local variable 'paramBuffer' holding an array of nsXPTCMiniVariant |
| // on the stack, which appears to be live across a GC call because its |
| // constructor is called when the array is initialized, even though the |
| // constructor is a no-op. So we'll do a very narrow exclusion for the use |
| // that incorrectly started the live range, which was basically "__temp_1 = |
| // paramBuffer". |
| // |
| // By scoping it so narrowly, we can detect most hazards that would be |
| // caused by modifications in the PrepareAndDispatch code. It just barely |
| // avoids having a hazard already. |
| if (('Name' in variable) && (variable.Name[0] == 'paramBuffer')) { |
| if (body.BlockId.Kind == 'Function' && body.BlockId.Variable.Name[0] == 'PrepareAndDispatch') |
| if (edge.Kind == 'Assign' && edge.Type.Kind == 'Pointer') |
| if (edge.Exp[0].Kind == 'Var' && edge.Exp[1].Kind == 'Var') |
| if (edge.Exp[1].Variable.Kind == 'Local' && edge.Exp[1].Variable.Name[0] == 'paramBuffer') |
| return true; |
| } |
| |
| // Functions which should not be treated as using variable. |
| if (edge.Kind == "Call") { |
| var callee = edge.Exp[0]; |
| if (callee.Kind == "Var") { |
| var name = callee.Variable.Name[0]; |
| if (/~DebugOnly/.test(name)) |
| return true; |
| if (/~ScopedThreadSafeStringInspector/.test(name)) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| function ignoreEdgeAddressTaken(edge) |
| { |
| // Functions which may take indirect pointers to unrooted GC things, |
| // but will copy them into rooted locations before calling anything |
| // that can GC. These parameters should usually be replaced with |
| // handles or mutable handles. |
| if (edge.Kind == "Call") { |
| var callee = edge.Exp[0]; |
| if (callee.Kind == "Var") { |
| var name = callee.Variable.Name[0]; |
| if (/js::Invoke\(/.test(name)) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Return whether csu.method is one that we claim can never GC. |
| function isSuppressedVirtualMethod(csu, method) |
| { |
| return csu == "nsISupports" && (method == "AddRef" || method == "Release"); |
| } |
| |
| // Ignore calls of these functions (so ignore any stack containing these) |
| var ignoreFunctions = { |
| "ptio.c:pt_MapError" : true, |
| "je_malloc_printf" : true, |
| "PR_ExplodeTime" : true, |
| "PR_ErrorInstallTable" : true, |
| "PR_SetThreadPrivate" : true, |
| "JSObject* js::GetWeakmapKeyDelegate(JSObject*)" : true, // FIXME: mark with AutoSuppressGCAnalysis instead |
| "uint8 NS_IsMainThread()" : true, |
| |
| // Has an indirect call under it by the name "__f", which seemed too |
| // generic to ignore by itself. |
| "void* std::_Locale_impl::~_Locale_impl(int32)" : true, |
| |
| // Bug 1056410 - devirtualization prevents the standard nsISupports::Release heuristic from working |
| "uint32 nsXPConnect::Release()" : true, |
| |
| // FIXME! |
| "NS_LogInit": true, |
| "NS_LogTerm": true, |
| "NS_LogAddRef": true, |
| "NS_LogRelease": true, |
| "NS_LogCtor": true, |
| "NS_LogDtor": true, |
| "NS_LogCOMPtrAddRef": true, |
| "NS_LogCOMPtrRelease": true, |
| |
| // FIXME! |
| "NS_DebugBreak": true, |
| |
| // These are a little overzealous -- these destructors *can* GC if they end |
| // up wrapping a pending exception. See bug 898815 for the heavyweight fix. |
| "void js::AutoCompartment::~AutoCompartment(int32)" : true, |
| "void JSAutoCompartment::~JSAutoCompartment(int32)" : true, |
| |
| // Bug 948646 - the only thing AutoJSContext's constructor calls |
| // is an Init() routine whose entire body is covered with an |
| // AutoSuppressGCAnalysis. AutoSafeJSContext is the same thing, just with |
| // a different value for the 'aSafe' parameter. |
| "void mozilla::AutoJSContext::AutoJSContext(mozilla::detail::GuardObjectNotifier*)" : true, |
| "void mozilla::AutoSafeJSContext::~AutoSafeJSContext(int32)" : true, |
| |
| // And these are workarounds to avoid even more analysis work, |
| // which would sadly still be needed even with bug 898815. |
| "void js::AutoCompartment::AutoCompartment(js::ExclusiveContext*, JSCompartment*)": true, |
| |
| // The nsScriptNameSpaceManager functions can't actually GC. They |
| // just use a PLDHashTable which has function pointers, which makes the |
| // analysis think maybe they can. |
| "nsGlobalNameStruct* nsScriptNameSpaceManager::LookupNavigatorName(nsAString_internal*)": true, |
| "nsGlobalNameStruct* nsScriptNameSpaceManager::LookupName(nsAString_internal*, uint16**)": true, |
| |
| // Similar to heap snapshot mock classes, and GTests below. This posts a |
| // synchronous runnable when a GTest fails, and we are pretty sure that the |
| // particular runnable it posts can't even GC, but the analysis isn't |
| // currently smart enough to determine that. In either case, this is (a) |
| // only in GTests, and (b) only when the Gtest has already failed. We have |
| // static and dynamic checks for no GC in the non-test code, and in the test |
| // code we fall back to only the dynamic checks. |
| "void test::RingbufferDumper::OnTestPartResult(testing::TestPartResult*)" : true, |
| |
| "float64 JS_GetCurrentEmbedderTime()" : true, |
| |
| "uint64 js::TenuringTracer::moveObjectToTenured(JSObject*, JSObject*, int32)" : true, |
| "uint32 js::TenuringTracer::moveObjectToTenured(JSObject*, JSObject*, int32)" : true, |
| }; |
| |
| function isProtobuf(name) |
| { |
| return name.match(/\bgoogle::protobuf\b/) || |
| name.match(/\bmozilla::devtools::protobuf\b/); |
| } |
| |
| function isHeapSnapshotMockClass(name) |
| { |
| return name.match(/\bMockWriter\b/) || |
| name.match(/\bMockDeserializedNode\b/); |
| } |
| |
| function isGTest(name) |
| { |
| return name.match(/\btesting::/); |
| } |
| |
| function ignoreGCFunction(mangled) |
| { |
| assert(mangled in readableNames); |
| var fun = readableNames[mangled][0]; |
| |
| if (fun in ignoreFunctions) |
| return true; |
| |
| // The protobuf library, and [de]serialization code generated by the |
| // protobuf compiler, uses a _ton_ of function pointers but they are all |
| // internal. Easiest to just ignore that mess here. |
| if (isProtobuf(fun)) |
| return true; |
| |
| // Ignore anything that goes through heap snapshot GTests or mocked classes |
| // used in heap snapshot GTests. GTest and GMock expose a lot of virtual |
| // methods and function pointers that could potentially GC after an |
| // assertion has already failed (depending on user-provided code), but don't |
| // exhibit that behavior currently. For non-test code, we have dynamic and |
| // static checks that ensure we don't GC. However, for test code we opt out |
| // of static checks here, because of the above stated GMock/GTest issues, |
| // and rely on only the dynamic checks provided by AutoAssertCannotGC. |
| if (isHeapSnapshotMockClass(fun) || isGTest(fun)) |
| return true; |
| |
| // Templatized function |
| if (fun.indexOf("void nsCOMPtr<T>::Assert_NoQueryNeeded()") >= 0) |
| return true; |
| |
| // These call through an 'op' function pointer. |
| if (fun.indexOf("js::WeakMap<Key, Value, HashPolicy>::getDelegate(") >= 0) |
| return true; |
| |
| // XXX modify refillFreeList<NoGC> to not need data flow analysis to understand it cannot GC. |
| if (/refillFreeList/.test(fun) && /\(js::AllowGC\)0u/.test(fun)) |
| return true; |
| return false; |
| } |
| |
| function stripUCSAndNamespace(name) |
| { |
| name = name.replace(/(struct|class|union|const) /g, ""); |
| name = name.replace(/(js::ctypes::|js::|JS::|mozilla::dom::|mozilla::)/g, ""); |
| return name; |
| } |
| |
| function isRootedGCTypeName(name) |
| { |
| return (name == "JSAddonId"); |
| } |
| |
| function isRootedGCPointerTypeName(name) |
| { |
| name = stripUCSAndNamespace(name); |
| |
| if (name.startsWith('MaybeRooted<')) |
| return /\(js::AllowGC\)1u>::RootType/.test(name); |
| |
| if (name == "ErrorResult" || |
| name == "JSErrorResult" || |
| name == "WrappableJSErrorResult" || |
| name == "frontend::TokenStream" || |
| name == "frontend::TokenStream::Position" || |
| name == "ModuleValidator") |
| { |
| return true; |
| } |
| |
| return name.startsWith('Rooted') || name.startsWith('PersistentRooted'); |
| } |
| |
| function isRootedTypeName(name) |
| { |
| return isRootedGCTypeName(name) || isRootedGCPointerTypeName(name); |
| } |
| |
| function isUnsafeStorage(typeName) |
| { |
| typeName = stripUCSAndNamespace(typeName); |
| return typeName.startsWith('UniquePtr<'); |
| } |
| |
| function isSuppressConstructor(funcName) |
| { |
| let [ qualifiedFunction, unqualifiedFunction ] = funcName; |
| let [ mangled, unmangled ] = splitFunction(qualifiedFunction); |
| if (!mangled.match(/C\dE/)) |
| return false; // Constructors have C1E (or C4E etc.) in their mangled names |
| let m = unmangled.match(/((\w+)(<.*>)?)::\2\(/); // Foo<T>::Foo |
| if (!m) |
| return false; |
| let classType = m[1]; // Foo<T> |
| for (let type of GCSuppressionTypes) { |
| // type is something like class js::Foo<T> |
| if (type.endsWith(classType)) { |
| // Screen out js::OtherFoo |
| if (type == classType || type.endsWith("::" + classType) || type.endsWith(" " + classType)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // nsISupports subclasses' methods may be scriptable (or overridden |
| // via binary XPCOM), and so may GC. But some fields just aren't going |
| // to get overridden with something that can GC. |
| function isOverridableField(initialCSU, csu, field) |
| { |
| if (csu != 'nsISupports') |
| return false; |
| if (field == 'GetCurrentJSContext') |
| return false; |
| if (field == 'IsOnCurrentThread') |
| return false; |
| if (field == 'GetNativeContext') |
| return false; |
| if (field == "GetGlobalJSObject") |
| return false; |
| if (field == "GetIsMainThread") |
| return false; |
| if (initialCSU == 'nsIXPConnectJSObjectHolder' && field == 'GetJSObject') |
| return false; |
| if (initialCSU == 'nsIXPConnect' && field == 'GetSafeJSContext') |
| return false; |
| if (initialCSU == 'nsIScriptContext') { |
| if (field == 'GetWindowProxy' || field == 'GetWindowProxyPreserveColor') |
| return false; |
| } |
| return true; |
| } |
| |
| function listNonGCPointers() { |
| return [ |
| // Safe only because jsids are currently only made from pinned strings. |
| 'NPIdentifier', |
| ]; |
| } |