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 }