summaryrefslogtreecommitdiffstats
path: root/js/src/devtools/rootAnalysis/annotations.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/devtools/rootAnalysis/annotations.js
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/devtools/rootAnalysis/annotations.js')
-rw-r--r--js/src/devtools/rootAnalysis/annotations.js529
1 files changed, 529 insertions, 0 deletions
diff --git a/js/src/devtools/rootAnalysis/annotations.js b/js/src/devtools/rootAnalysis/annotations.js
new file mode 100644
index 0000000000..93d022dd83
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -0,0 +1,529 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+
+"use strict";
+
+// Ignore calls made through these function pointers
+var ignoreIndirectCalls = {
+ "mallocSizeOf" : true,
+ "aMallocSizeOf" : true,
+ "__conv" : true,
+ "__convf" : true,
+ "prerrortable.c:callback_newtable" : true,
+ "mozalloc_oom.cpp:void (* gAbortHandler)(size_t)" : true,
+};
+
+// Types that when constructed with no arguments, are "safe" values (they do
+// not contain GC pointers).
+var typesWithSafeConstructors = new Set([
+ "mozilla::Maybe",
+ "mozilla::dom::Nullable",
+ "mozilla::dom::Optional",
+ "mozilla::UniquePtr",
+ "js::UniquePtr"
+]);
+
+var resetterMethods = {
+ 'mozilla::Maybe': new Set(["reset"]),
+ 'mozilla::UniquePtr': new Set(["reset"]),
+ 'js::UniquePtr': new Set(["reset"]),
+ 'mozilla::dom::Nullable': new Set(["SetNull"]),
+ 'mozilla::dom::TypedArray_base': new Set(["Reset"]),
+};
+
+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;
+
+ // hook called during script finalization which cannot GC.
+ if (/CallDestroyScriptHook/.test(caller))
+ return true;
+
+ // Call through a 'callback' function pointer, in a place where we're going
+ // to be throwing a JS exception.
+ if (name == "callback" && caller.includes("js::ErrorToException"))
+ return true;
+
+ // The math cache only gets called with non-GC math functions.
+ if (name == "f" && caller.includes("js::MathCache::lookup"))
+ return true;
+
+ // It would probably be better to somehow rewrite PR_CallOnce(foo) into a
+ // call of foo, but for now just assume that nobody is crazy enough to use
+ // PR_CallOnce with a function that can GC.
+ if (name == "func" && caller == "PR_CallOnce")
+ 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,
+ "_MD_IOVector" : true,
+ "malloc_table_t": true, // replace_malloc
+ "malloc_hook_table_t": true, // replace_malloc
+ "mozilla::MallocSizeOf": true,
+ "MozMallocSizeOf": true,
+};
+
+// 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,
+ "JSClassOps.trace" : true,
+ "JSClassOps.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,
+ "PLDHashTableOps.clearEntry" : 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 (*)(JSContext*, void*)>.op" : true,
+ "mozilla::ThreadSharedFloatArrayBufferList::Storage.mFree" : true,
+ "mozilla::SizeOfState.mMallocSizeOf": 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,
+ "malloc_usable_size" : true,
+ "vprintf_stderr" : true,
+ "PR_ExplodeTime" : true,
+ "PR_ErrorInstallTable" : true,
+ "PR_SetThreadPrivate" : true,
+ "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,
+ "uint32 nsAtom::Release()" : true,
+
+ // Allocation API
+ "malloc": true,
+ "calloc": true,
+ "realloc": true,
+ "free": 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,
+
+ // 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,
+
+ // This calls any JSObjectMovedOp for the tenured object via an indirect call.
+ "JSObject* js::TenuringTracer::moveToTenuredSlow(JSObject*)" : true,
+
+ "void js::Nursery::freeMallocedBuffers()" : true,
+
+ "void js::AutoEnterOOMUnsafeRegion::crash(uint64, int8*)" : true,
+
+ "void mozilla::dom::WorkerPrivate::AssertIsOnWorkerThread() const" : true,
+
+ // It would be cool to somehow annotate that nsTHashtable<T> will use
+ // nsTHashtable<T>::s_MatchEntry for its matchEntry function pointer, but
+ // there is no mechanism for that. So we will just annotate a particularly
+ // troublesome logging-related usage.
+ "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,
+ "EntryType* nsTHashtable<EntryType>::GetEntry(nsTHashtable<EntryType>::KeyType) const [with EntryType = nsBaseHashtableET<nsCharPtrHashKey, nsAutoPtr<mozilla::LogModule> >; nsTHashtable<EntryType>::KeyType = const char*]" : true,
+ "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,
+ "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,
+
+ // VTune internals that lazy-load a shared library and make IndirectCalls.
+ "iJIT_IsProfilingActive" : true,
+ "iJIT_NotifyEvent": true,
+
+ // The big hammers.
+ "PR_GetCurrentThread" : true,
+ "calloc" : true,
+
+ // This will happen early enough in initialization to not matter.
+ "_PR_UnixInit" : true,
+
+ "uint8 nsContentUtils::IsExpandedPrincipal(nsIPrincipal*)" : true,
+
+ "void mozilla::AutoProfilerLabel::~AutoProfilerLabel(int32)" : true,
+
+ // Stores a function pointer in an AutoProfilerLabelData struct and calls it.
+ // And it's in mozglue, which doesn't have access to the attributes yet.
+ "void mozilla::ProfilerLabelEnd(mozilla::Tuple<void*, unsigned int>*)" : true,
+
+ // This gets into PLDHashTable function pointer territory, and should get
+ // set up early enough to not do anything when it matters anyway.
+ "mozilla::LogModule* mozilla::LogModule::Get(int8*)": true,
+
+ // This annotation is correct, but the reasoning is still being hashed out
+ // in bug 1582326 comment 8 and on.
+ "nsCycleCollector.cpp:nsISupports* CanonicalizeXPCOMParticipant(nsISupports*)": true,
+
+ // PLDHashTable again
+ "void mozilla::DeadlockDetector<T>::Add(const T*) [with T = mozilla::BlockingResourceBase]": true,
+
+ // OOM handling during logging
+ "void mozilla::detail::log_print(mozilla::LogModule*, int32, int8*)": true,
+
+ // This would need to know that the nsCOMPtr refcount will not go to zero.
+ "uint8 XPCJSRuntime::DescribeCustomObjects(JSObject*, JSClass*, int8[72]*)[72]) const": true,
+
+ // As the comment says "Refcount isn't zero, so Suspect won't delete anything."
+ "uint64 nsCycleCollectingAutoRefCnt::incr(void*, nsCycleCollectionParticipant*) [with void (* suspect)(void*, nsCycleCollectionParticipant*, nsCycleCollectingAutoRefCnt*, bool*) = NS_CycleCollectorSuspect3; uintptr_t = long unsigned int]": true,
+
+ // Calls MergeSort
+ "uint8 v8::internal::RegExpDisjunction::SortConsecutiveAtoms(v8::internal::RegExpCompiler*)": true,
+
+ // nsIEventTarget.IsOnCurrentThreadInfallible does not get resolved, and
+ // this is called on non-JS threads so cannot use AutoSuppressGCAnalysis.
+ "uint8 nsAutoOwningEventTarget::IsCurrentThread() const": true,
+};
+
+function extraGCFunctions() {
+ return ["ffi_call"].filter(f => f in readableNames);
+}
+
+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 isICU(name)
+{
+ return name.match(/\bicu_\d+::/) ||
+ name.match(/u(prv_malloc|prv_realloc|prv_free|case_toFullLower)_\d+/)
+}
+
+function ignoreGCFunction(mangled)
+{
+ // Field calls will not be in readableNames
+ if (!(mangled in readableNames))
+ return false;
+
+ const 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. The same is true for ICU. Easiest to just ignore that mess
+ // here.
+ if (isProtobuf(fun) || isICU(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.includes("void nsCOMPtr<T>::Assert_NoQueryNeeded()"))
+ return true;
+
+ // Bug 1577915 - Sixgill is ignoring a template param that makes its CFG
+ // impossible.
+ if (fun.includes("UnwrapObjectInternal") && fun.includes("mayBeWrapper = false"))
+ return true;
+
+ // These call through an 'op' function pointer.
+ if (fun.includes("js::WeakMap<Key, Value, HashPolicy>::getDelegate("))
+ return true;
+
+ // TODO: modify refillFreeList<NoGC> to not need data flow analysis to
+ // understand it cannot GC. As of gcc 6, the same problem occurs with
+ // tryNewTenuredThing, tryNewNurseryObject, and others.
+ if (/refillFreeList|tryNew/.test(fun) && /= js::NoGC/.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 extraRootedGCThings()
+{
+ return [ 'JSAddonId' ];
+}
+
+function extraRootedPointers()
+{
+ return [
+ ];
+}
+
+function isRootedGCPointerTypeName(name)
+{
+ name = stripUCSAndNamespace(name);
+
+ if (name.startsWith('MaybeRooted<'))
+ return /\(js::AllowGC\)1u>::RootType/.test(name);
+
+ return false;
+}
+
+function isUnsafeStorage(typeName)
+{
+ typeName = stripUCSAndNamespace(typeName);
+ return typeName.startsWith('UniquePtr<');
+}
+
+// If edgeType is a constructor type, return whatever limits it implies for its
+// scope (or zero if not matching).
+function isLimitConstructor(typeInfo, edgeType, varName)
+{
+ // Check whether this could be a constructor
+ if (edgeType.Kind != 'Function')
+ return 0;
+ if (!('TypeFunctionCSU' in edgeType))
+ return 0;
+ if (edgeType.Type.Kind != 'Void')
+ return 0;
+
+ // Check whether the type is a known suppression type.
+ var type = edgeType.TypeFunctionCSU.Type.Name;
+ let limit = 0;
+ if (type in typeInfo.GCSuppressors)
+ limit = limit | LIMIT_CANNOT_GC;
+
+ // And now make sure this is the constructor, not some other method on a
+ // suppression type. varName[0] contains the qualified name.
+ var [ mangled, unmangled ] = splitFunction(varName[0]);
+ if (mangled.search(/C\d[EI]/) == -1)
+ return 0; // Mangled names of constructors have C<num>E or C<num>I
+ var m = unmangled.match(/([~\w]+)(?:<.*>)?\(/);
+ if (!m)
+ return 0;
+ var type_stem = type.replace(/\w+::/g, '').replace(/\<.*\>/g, '');
+ if (m[1] != type_stem)
+ return 0;
+
+ return limit;
+}
+
+// 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(staticCSU, csu, field)
+{
+ if (csu != 'nsISupports')
+ return false;
+
+ // Now that binary XPCOM is dead, all these annotations should be replaced
+ // with something based on bug 1347999.
+ if (field == 'GetCurrentJSContext')
+ return false;
+ if (field == 'IsOnCurrentThread')
+ return false;
+ if (field == 'GetNativeContext')
+ return false;
+ if (field == "GetGlobalJSObject")
+ return false;
+ if (field == "GetGlobalJSObjectPreserveColor")
+ return false;
+ if (field == "GetIsMainThread")
+ return false;
+ if (field == "GetThreadFromPRThread")
+ return false;
+ if (field == "DocAddSizeOfIncludingThis")
+ return false;
+ if (field == "ConstructUbiNode")
+ return false;
+
+ // Fields on the [builtinclass] nsIPrincipal
+ if (field == "GetSiteOrigin")
+ return false;
+ if (field == "GetDomain")
+ return false;
+ if (field == "GetBaseDomain")
+ return false;
+ if (field == "GetOriginNoSuffix")
+ return false;
+
+ // Fields on nsIURI
+ if (field == "GetScheme")
+ return false;
+ if (field == "GetAsciiHostPort")
+ return false;
+ if (field == "GetAsciiSpec")
+ return false;
+ if (field == "SchemeIs")
+ return false;
+
+ if (staticCSU == 'nsIXPCScriptable' && field == "GetScriptableFlags")
+ return false;
+ if (staticCSU == 'nsIXPConnectJSObjectHolder' && field == 'GetJSObject')
+ return false;
+ if (staticCSU == 'nsIXPConnect' && field == 'GetSafeJSContext')
+ return false;
+
+ // nsIScriptSecurityManager is not [builtinclass], but smaug says "the
+ // interface definitely should be builtinclass", which is good enough.
+ if (staticCSU == 'nsIScriptSecurityManager' && field == 'IsSystemPrincipal')
+ return false;
+
+ if (staticCSU == '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',
+ ];
+}
+
+function isJSNative(mangled)
+{
+ // _Z...E = function
+ // 9JSContext = JSContext*
+ // j = uint32
+ // PN2JS5Value = JS::Value*
+ // P = pointer
+ // N2JS = JS::
+ // 5Value = Value
+ return mangled.endsWith("P9JSContextjPN2JS5ValueE") && mangled.startsWith("_Z");
+}