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 }