/* 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/. */
function checkExternalFunction(entry)
{ var whitelist = [ "__builtin_clz", "__builtin_expect", "isprint", "ceilf", "floorf",
/^rusturl/, "memcmp", "strcmp", "fmod", "floor", "ceil", "atof",
/memchr/, "strlen",
/Servo_DeclarationBlock_GetCssText/, "Servo_GetArcStringData", "Servo_IsWorkerThread",
/nsIFrame::AppendOwnedAnonBoxes/, // Assume that atomic accesses are threadsafe.
/^__atomic_/,
]; if (entry.matches(whitelist)) return;
// memcpy and memset are safe if the target pointer is threadsafe. const simpleWrites = [ "memcpy", "memset", "memmove",
];
if (entry.isSafeArgument(1) && simpleWrites.includes(entry.name)) return;
dumpError(entry, null, "External function");
}
function hasThreadsafeReferenceCounts(entry, regexp)
{ // regexp should match some nsISupports-operating function and produce the // name of the nsISupports class via exec().
// nsISupports classes which have threadsafe reference counting. var whitelist = [ "nsIRunnable",
// I don't know if these always have threadsafe refcounts. "nsAtom", "nsIPermissionManager", "nsIURI",
];
var match = regexp.exec(entry.name); return match && nameMatchesArray(match[1], whitelist);
}
function checkOverridableVirtualCall(entry, location, callee)
{ // We get here when a virtual call is made on a structure which might be // overridden by script or by a binary extension. This includes almost // everything under nsISupports, however, so for the most part we ignore // this issue. The exception is for nsISupports AddRef/Release, which are // not in general threadsafe and whose overrides will not be generated by // the callgraph analysis. if (callee != "nsISupports.AddRef" && callee != "nsISupports.Release") return;
if (hasThreadsafeReferenceCounts(entry, /::~?nsCOMPtr\(.*?\[with T = (.*?)\]$/)) return; if (hasThreadsafeReferenceCounts(entry, /RefPtrTraits.*?::Release.*?\[with U = (.*?)\]/)) return; if (hasThreadsafeReferenceCounts(entry, /nsCOMPtr<T>::assign_assuming_AddRef.*?\[with T = (.*?)\]/)) return; if (hasThreadsafeReferenceCounts(entry, /nsCOMPtr<T>::assign_with_AddRef.*?\[with T = (.*?)\]/)) return;
// Watch for raw addref/release. var whitelist = [ "Gecko_AddRefAtom", "Gecko_ReleaseAtom",
/nsPrincipal::Get/,
]; if (entry.matches(whitelist)) return;
dumpError(entry, location, "AddRef/Release on nsISupports");
}
function checkIndirectCall(entry, location, callee)
{ var name = entry.name;
// These hash table callbacks should be threadsafe. if (/PLDHashTable/.test(name) && (/matchEntry/.test(callee) || /hashKey/.test(callee))) return; if (/PL_HashTable/.test(name) && /keyCompare/.test(callee)) return;
// Annotations for function parameters, based on function name and parameter // name + type. function treatAsSafeArgument(entry, varName, csuName)
{ var whitelist = [ // These iterator classes should all be thread local. They are passed // in to some Servo bindings and are created on the heap by others, so // just ignore writes to them.
[null, null, /StyleChildrenIterator/],
[null, null, /ExplicitChildIterator/],
// The use of BeginReading() to instantiate this class confuses the // analysis.
[null, null, /nsReadingIterator/],
// These classes are passed to some Servo bindings to fill in.
[/^Gecko_/, null, "nsStyleImageLayers"],
[/^Gecko_/, null, /FontFamilyList/],
function isSafeAssignment(entry, edge, variable)
{ if (edge.Kind != 'Assign') returnfalse;
var [mangled, unmangled] = splitFunction(entry.name);
// The assignment // // nsFont* font = fontTypes[eType]; // // ends up with 'font' pointing to a member of 'this', so it should inherit // the safety of 'this'. if (unmangled.includes("mozilla::LangGroupFontPrefs::Initialize") &&
variable == 'font')
{ const [lhs, rhs] = edge.Exp; const {Kind, Exp: [{Kind: indexKind, Exp: [collection, index]}]} = rhs; if (Kind == 'Drf' &&
indexKind == 'Index' &&
collection.Kind == 'Var' &&
collection.Variable.Name[0] == 'fontTypes')
{ return entry.isSafeArgument(0); // 'this'
}
}
returnfalse;
}
function checkFieldWrite(entry, location, fields)
{ var name = entry.name; for (var field of fields) { // The analysis is having some trouble keeping track of whether // already_AddRefed and nsCOMPtr structures are safe to access. // Hopefully these will be thread local, but it would be better to // improve the analysis to handle these. if (/already_AddRefed.*?.mRawPtr/.test(field)) return; if (/nsCOMPtr<.*?>.mRawPtr/.test(field)) return;
if (/\bThreadLocal<\b/.test(field)) return;
}
var str = ""; for (var field of fields)
str += " " + field;
function checkDereferenceWrite(entry, location, variable)
{ var name = entry.name;
// Maybe<T> uses placement new on local storage in a way we don't understand. // Allow this if the Maybe<> value itself is threadsafe. if (/Maybe.*?::emplace/.test(name) && entry.isSafeArgument(0)) return;
// UniquePtr writes through temporaries referring to its internal storage. // Allow this if the UniquePtr<> is threadsafe. if (/UniquePtr.*?::reset/.test(name) && entry.isSafeArgument(0)) return;
// Operations on nsISupports reference counts. if (hasThreadsafeReferenceCounts(entry, /nsCOMPtr<T>::swap\(.*?\[with T = (.*?)\]/)) return;
// ConvertToLowerCase::write writes through a local pointer into the first // argument. if (/ConvertToLowerCase::write/.test(name) && entry.isSafeArgument(0)) return;
function ignoreCallEdge(entry, callee)
{ var name = entry.name;
// nsPropertyTable::GetPropertyInternal has the option of removing data // from the table, but when it is called by nsPropertyTable::GetProperty // this will not occur. if (/nsPropertyTable::GetPropertyInternal/.test(callee) &&
/nsPropertyTable::GetProperty/.test(name))
{ returntrue;
}
// Document::PropertyTable calls GetExtraPropertyTable (which has side // effects) if the input category is non-zero. If a literal zero was passed // in for the category then we treat it as a safe argument, per // isEdgeSafeArgument, so just watch for that. if (/Document::GetExtraPropertyTable/.test(callee) &&
/Document::PropertyTable/.test(name) &&
entry.isSafeArgument(1))
{ returntrue;
}
// CachedBorderImageData is exclusively owned by nsStyleImage, but the // analysis is not smart enough to know this. if (/CachedBorderImageData::PurgeCachedImages/.test(callee) &&
/nsStyleImage::/.test(name) &&
entry.isSafeArgument(0))
{ returntrue;
}
// StyleShapeSource exclusively owns its UniquePtr<nsStyleImage>. if (/nsStyleImage::SetURLValue/.test(callee) &&
/StyleShapeSource::SetURL/.test(name) &&
entry.isSafeArgument(0))
{ returntrue;
}
// The AddRef through a just-assigned heap pointer here is not handled by // the analysis. if (/nsCSSValue::Array::AddRef/.test(callee) &&
/nsStyleContentData::SetCounters/.test(name) &&
entry.isSafeArgument(2))
{ returntrue;
}
// AllChildrenIterator asks AppendOwnedAnonBoxes to append into an nsTArray // local variable. if (/nsIFrame::AppendOwnedAnonBoxes/.test(callee) &&
/AllChildrenIterator::AppendNativeAnonymousChildren/.test(name))
{ returntrue;
}
// Runnables are created and named on one thread, then dispatched // (possibly to another). Writes on the origin thread are ok. if (/::SetName/.test(callee) &&
/::UnlabeledDispatch/.test(name))
{ returntrue;
}
// We manually lock here if (name == "Gecko_nsFont_InitSystem" ||
name == "Gecko_GetFontMetrics" ||
name == "Gecko_nsStyleFont_FixupMinFontSize" ||
/ThreadSafeGetDefaultFontHelper/.test(name))
{ returntrue;
}
returnfalse;
}
function ignoreContents(entry)
{ var whitelist = [ // We don't care what happens when we're about to crash. "abort",
/MOZ_ReportAssertionFailure/,
/MOZ_ReportCrash/,
/MOZ_Crash/,
/MOZ_CrashPrintf/,
/AnnotateMozCrashReason/,
/InvalidArrayIndex_CRASH/,
/NS_ABORT_OOM/,
// These all create static strings in local storage, which is threadsafe // to do but not understood by the analysis yet.
/ EmptyString\(\)/,
// These could probably be handled by treating the scope of PSAutoLock // aka BaseAutoLock<PSMutex> as threadsafe.
/profiler_register_thread/,
/profiler_unregister_thread/,
// The analysis thinks we'll write to mBits in the DoGetStyleFoo<false> // call. Maybe the template parameter confuses it?
/ComputedStyle::PeekStyle/,
// The analysis can't cope with the indirection used for the objects // being initialized here, from nsCSSValue::Array::Create to the return // value of the Item(i) getter.
/nsCSSValue::SetCalcValue/,
// Unable to analyze safety of linked list initialization. "Gecko_NewCSSValueSharedList", "Gecko_CSSValue_InitSharedList",
// Unable to trace through dataflow, but straightforward if inspected. "Gecko_NewNoneTransform",
// Need main thread assertions or other fixes.
/EffectCompositor::GetServoAnimationRule/,
]; if (entry.matches(whitelist)) returntrue;
if (entry.isSafeArgument(0)) { var heapWhitelist = [ // Operations on heap structures pointed to by arrays and strings are // threadsafe as long as the array/string itself is threadsafe.
/nsTArray_Impl.*?::AppendElement/,
/nsTArray_Impl.*?::RemoveElementsAt/,
/nsTArray_Impl.*?::ReplaceElementsAt/,
/nsTArray_Impl.*?::InsertElementAt/,
/nsTArray_Impl.*?::SetCapacity/,
/nsTArray_Impl.*?::SetLength/,
/nsTArray_base.*?::EnsureCapacity/,
/nsTArray_base.*?::ShiftData/,
/AutoTArray.*?::Init/,
/(nsTSubstring<T>|nsAC?String)::SetCapacity/,
/(nsTSubstring<T>|nsAC?String)::SetLength/,
/(nsTSubstring<T>|nsAC?String)::Assign/,
/(nsTSubstring<T>|nsAC?String)::Append/,
/(nsTSubstring<T>|nsAC?String)::Replace/,
/(nsTSubstring<T>|nsAC?String)::Trim/,
/(nsTSubstring<T>|nsAC?String)::Truncate/,
/(nsTSubstring<T>|nsAC?String)::StripTaggedASCII/,
/(nsTSubstring<T>|nsAC?String)::operator=/,
/nsTAutoStringN<T, N>::nsTAutoStringN/,
// Similar for some other data structures
/nsCOMArray_base::SetCapacity/,
/nsCOMArray_base::Clear/,
/nsCOMArray_base::AppendElement/,
// UniquePtr is similar.
/mozilla::UniquePtr/,
// The use of unique pointers when copying mCropRect here confuses // the analysis.
/nsStyleImage::DoCopy/,
]; if (entry.matches(heapWhitelist)) returntrue;
}
if (entry.isSafeArgument(1)) { var firstArgWhitelist = [
/nsTextFormatter::snprintf/,
/nsTextFormatter::ssprintf/,
/_ASCIIToUpperInSitu/,
// Handle some writes into an array whose safety we don't have a good way // of tracking currently.
/FillImageLayerList/,
/FillImageLayerPositionCoordList/,
]; if (entry.matches(firstArgWhitelist)) returntrue;
}
if (entry.isSafeArgument(2)) { var secondArgWhitelist = [
/StringBuffer::ToString/,
/AppendUTF\d+toUTF\d+/,
/AppendASCIItoUTF\d+/,
]; if (entry.matches(secondArgWhitelist)) returntrue;
}
function stripFields(exp)
{ // Fields and index operations do not involve any dereferences. Remove them // from the expression but remember any encountered fields for use by // annotations later on. var fields = []; while (true) { if (exp.Kind == "Index") {
exp = exp.Exp[0]; continue;
} if (exp.Kind == "Fld") { var csuName = exp.Field.FieldCSU.Type.Name; var fieldName = exp.Field.Name[0]; assert(csuName && fieldName);
fields.push(csuName + "." + fieldName);
exp = exp.Exp[0]; continue;
} break;
} return [exp, fields];
}
// Safe arguments are those which may be written through (directly, not through // pointer fields etc.) without concerns about thread safety. This includes // pointers to stack data, null pointers, and other data we know is thread // local, such as certain arguments to the root functions. // // Entries in the worklist keep track of the pointer arguments to the function // which are safe using a sorted array, so that this can be propagated down the // stack. Zero is |this|, and arguments are indexed starting at one.
// We want to suppress output for functions that ended up not having any // hazards, for brevity of the final output. So each new toplevel function will // initialize this to a string, which should be printed only if an error is // seen. var errorHeader;
var startTime = new Date; function elapsedTime()
{ var seconds = (new Date - startTime) / 1000; return"[" + seconds.toFixed(2) + "s] ";
}
var minStream = xdb.min_data_stream(); var maxStream = xdb.max_data_stream(); var roots = [];
var [flag, arg] = scriptArgs; if (flag && (flag == '-f' || flag == '--function')) {
roots = [arg];
} else { for (var bodyIndex = minStream; bodyIndex <= maxStream; bodyIndex++) { var key = xdb.read_key(bodyIndex); var name = key.readString(); if (/^Gecko_/.test(name)) { var data = xdb.read_entry(key); if (/ServoBindings.cpp/.test(data.readString()))
roots.push(name);
xdb.free_string(data);
}
xdb.free_string(key);
}
}
print(elapsedTime() + "Found " + roots.length + " roots."); for (var i = 0; i < roots.length; i++) { var root = roots[i];
errorHeader = elapsedTime() + "#" + (i + 1) + " Analyzing " + root + " ..."; try {
processRoot(root);
} catch (e) { if (e != "Error!") throw e;
}
}
print(`${elapsedTime()}Completed analysis, found ${errorCount}/${errorLimit} allowed errors`);
var currentBody;
// All local variable assignments we have seen in either the outer or inner // function. This crosses loop boundaries, and currently has an unsoundness // where later assignments in a loop are not taken into account. var assignments;
// All loops in the current function which are reachable off main thread. var reachableLoops;
// Functions that are reachable from the current root. var reachable = {};
function dumpError(entry, location, text)
{ if (errorHeader) {
print(errorHeader);
errorHeader = undefined;
}
var stack = entry.stack;
print("Error: " + text);
print("Location: " + entry.name + (location ? " @ " + location : "") + stack[0].safeString());
print("Stack Trace:"); // Include the callers in the stack trace instead of the callees. Make sure // the dummy stack entry we added for the original roots is in place. assert(stack[stack.length - 1].location == null); for (var i = 0; i < stack.length - 1; i++)
print(stack[i + 1].callee + " @ " + stack[i].location + stack[i + 1].safeString());
print("\n");
if (++errorCount == errorLimit) {
print("Maximum number of errors encountered, exiting...");
quit();
}
throw"Error!";
}
// If edge is an assignment from a local variable, return the rhs variable. function variableAssignRhs(edge)
{ if (edge.Kind == "Assign" && edge.Exp[1].Kind == "Drf" && edge.Exp[1].Exp[0].Kind == "Var") { var variable = edge.Exp[1].Exp[0].Variable; if (isLocalVariable(variable)) return variable;
} returnnull;
}
function processAssign(body, entry, location, lhs, edge)
{ var fields;
[lhs, fields] = stripFields(lhs);
switch (lhs.Kind) { case"Var": var name = variableName(lhs.Variable); if (isLocalVariable(lhs.Variable)) { // Remember any assignments to local variables in this function. // Note that we ignore any points where the variable's address is // taken and indirect assignments might occur. This is an // unsoundness in the analysis.
let assign = [body, edge];
// Chain assignments if the RHS has only been assigned once. var rhsVariable = variableAssignRhs(edge); if (rhsVariable) { var rhsAssign = singleAssignment(variableName(rhsVariable)); if (rhsAssign)
assign = rhsAssign;
}
function process(entry, body, addCallee)
{ if (!("PEdge" in body)) return;
// Add any arguments which are safe due to annotations. if ("DefineVariable" in body) { for (var defvar of body.DefineVariable) { var index = safeArgumentIndex(defvar.Variable); if (index >= 0) { var varName = index ? variableName(defvar.Variable) : "this"; assert(varName);
entry.setParameterName(index, varName); var csuName = null; var type = defvar.Type; if (type.Kind == "Pointer" && type.Type.Kind == "CSU")
csuName = type.Type.Name; if (treatAsSafeArgument(entry, varName, csuName))
entry.addSafeArgument(index);
}
}
}
// Points in the body which are reachable if we are not on the main thread. var nonMainThreadPoints = [];
nonMainThreadPoints[body.Index[0]] = true;
for (var edge of body.PEdge) { // Ignore code that only executes on the main thread. if (!(edge.Index[0] in nonMainThreadPoints)) continue;
var location = get_location(body.PPoint[edge.Index[0] - 1].Location);
var callees = getCallees(edge); for (var callee of callees) { switch (callee.kind) { case"direct": var safeArguments = getEdgeSafeArguments(entry, edge, callee.name);
addCallee(new CallSite(callee.name, safeArguments, location, {})); break; case"resolved-field": break; case"field": var field = callee.csu + "." + callee.field; if (callee.isVirtual)
checkOverridableVirtualCall(entry, location, field); else
checkIndirectCall(entry, location, field); break; case"indirect":
checkIndirectCall(entry, location, callee.variable); break; default:
dumpError(entry, location, "Unknown call " + callee.kind); break;
}
}
// Treat assertion failures as if they don't return, so that // asserting NS_IsMainThread() is sufficient to prevent the // analysis from considering a block of code. if (isDirectCall(edge, /MOZ_ReportAssertionFailure/))
fallthrough = false;
} elseif (edge.Kind == "Loop") {
reachableLoops[edge.BlockId.Loop] = true;
} elseif (edge.Kind == "Assume") { if (testFailsOffMainThread(edge.Exp[0], edge.PEdgeAssumeNonZero))
fallthrough = false;
}
if (fallthrough)
nonMainThreadPoints[edge.Index[1]] = true;
}
}
function maybeProcessMissingFunction(entry, addCallee)
{ // If a function is missing it might be because a destructor Foo::~Foo() is // being called but GCC only gave us an implementation for // Foo::~Foo(int32). See computeCallgraph.js for a little more info. var name = entry.name; if (name.indexOf("::~") > 0 && name.indexOf("()") > 0) { var callee = name.replace("()", "(int32)");
addCallee(new CallSite(name, entry.safeArguments, entry.stack[0].location, entry.parameterNames)); returntrue;
}
// Similarly, a call to a C1 constructor might invoke the C4 constructor. A // mangled constructor will be something like _ZN<length><name>C1E... or in // the case of a templatized constructor, _ZN<length><name>C1I...EE... so // we hack it and look for "C1E" or "C1I" and replace them with their C4 // variants. This will have rare false matches, but so far we haven't hit // any external function calls of that sort. if (entry.mangledName().includes("C1E") || entry.mangledName().includes("C1I")) { var callee = name.replace("C1E", "C4E").replace("C1I", "C4I");
addCallee(new CallSite(name, entry.safeArguments, entry.stack[0].location, entry.parameterNames)); returntrue;
}
// Hack to manually follow some typedefs that show up on some functions. // This is a bug in the sixgill GCC plugin I think, since sixgill is // supposed to follow any typedefs itself. if (/mozilla::dom::Element/.test(name)) { var callee = name.replace("mozilla::dom::Element", "Document::Element");
addCallee(new CallSite(name, entry.safeArguments, entry.stack[0].location, entry.parameterNames)); returntrue;
}
// Hack for contravariant return types. When overriding a virtual method // with a method that returns a different return type (a subtype of the // original return type), we are getting the right mangled name but the // wrong return type in the unmangled name. if (/\$nsTextFrame*/.test(name)) { var callee = name.replace("nsTextFrame", "nsIFrame");
addCallee(new CallSite(name, entry.safeArguments, entry.stack[0].location, entry.parameterNames)); returntrue;
}
returnfalse;
}
function processRoot(name)
{ var safeArguments = []; var parameterNames = {}; var worklist = [new WorklistEntry(name, safeArguments, [new CallSite(name, safeArguments, null, parameterNames)], parameterNames)];
reachable = {};
while (worklist.length > 0) { var entry = worklist.pop();
// In principle we would be better off doing a meet-over-paths here to get // the common subset of arguments which are safe to write through. However, // analyzing functions separately for each subset if simpler, ensures that // the stack traces we produce accurately characterize the stack arguments, // and should be fast enough for now.
if (entry.mangledName() in reachable) continue;
reachable[entry.mangledName()] = true;
if (ignoreContents(entry)) continue;
var data = xdb.read_entry(entry.name); var dataString = data.readString(); var callees = []; if (dataString.length) { // Reverse the order of the bodies we process so that we visit the // outer function and see its assignments before the inner loops.
assignments = {};
reachableLoops = {}; var bodies = JSON.parse(dataString).reverse(); for (var body of bodies) { if (!body.BlockId.Loop || body.BlockId.Loop in reachableLoops) {
currentBody = body;
process(entry, body, Array.prototype.push.bind(callees));
}
}
} else { if (!maybeProcessMissingFunction(entry, Array.prototype.push.bind(callees)))
checkExternalFunction(entry);
}
xdb.free_string(data);
for (var callee of callees) { if (!ignoreCallEdge(entry, callee.callee)) { var nstack = [callee, ...entry.stack];
worklist.push(new WorklistEntry(callee.callee, callee.safeArguments, nstack, callee.parameterNames));
}
}
}
}
function isEdgeSafeArgument(entry, exp)
{ var fields;
[exp, fields] = stripFields(exp);
if (exp.Kind == "Var" && isLocalVariable(exp.Variable)) returntrue; if (exp.Kind == "Drf" && exp.Exp[0].Kind == "Var") { var variable = exp.Exp[0].Variable; return isSafeVariable(entry, variable);
} if (isZero(exp)) returntrue; returnfalse;
}
function getEdgeSafeArguments(entry, edge, callee)
{ assert(edge.Kind == "Call"); var res = []; if ("PEdgeCallInstance" in edge) { if (isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
res.push(0);
} if ("PEdgeCallArguments" in edge) { var args = edge.PEdgeCallArguments.Exp; for (var i = 0; i < args.length; i++) { if (isEdgeSafeArgument(entry, args[i]))
res.push(i + 1);
}
} return res;
}
function singleAssignment(name)
{ if (name in assignments) { var edges = assignments[name]; if (edges.length == 1) return edges[0];
} returnnull;
}
function expressionValueEdge(exp) { if (!(exp.Kind == "Var" && exp.Variable.Kind == "Temp")) returnnull; const assign = singleAssignment(variableName(exp.Variable)); if (!assign) returnnull; const [body, edge] = assign; return edge;
}
// Examples: // // void foo(type* aSafe) { // type* safeBecauseNew = new type(...); // type* unsafeBecauseMultipleAssignments = new type(...); // if (rand()) // unsafeBecauseMultipleAssignments = bar(); // type* safeBecauseSingleAssignmentOfSafe = aSafe; // } // function isSafeVariable(entry, variable)
{ var index = safeArgumentIndex(variable); if (index >= 0) return entry.isSafeArgument(index);
if (variable.Kind != "Temp" && variable.Kind != "Local") returnfalse; var name = variableName(variable);
if (!entry.safeLocals)
entry.safeLocals = new Map; if (entry.safeLocals.has(name)) return entry.safeLocals.get(name);
function isSafeLocalVariable(entry, name)
{ // If there is a single place where this variable has been assigned on // edges we are considering, look at that edge. var assign = singleAssignment(name); if (assign) { const [body, edge] = assign;
// Treat temporary pointers to DebugOnly contents as thread local. if (isDirectCall(edge, /DebugOnly.*?::operator/)) returntrue;
// Treat heap allocated pointers as thread local during construction. // Hopefully the construction code doesn't leak pointers to the object // to places where other threads might access it. if (isDirectCall(edge, /operator new/) ||
isDirectCall(edge, /nsCSSValue::Array::Create/))
{ returntrue;
}
if ("PEdgeCallInstance" in edge) { // References to the contents of an array are threadsafe if the array // itself is threadsafe. if ((isDirectCall(edge, /operator\[\]/) ||
isDirectCall(edge, /nsTArray.*?::InsertElementAt\b/) ||
isDirectCall(edge, /nsStyleContent::ContentAt/) ||
isDirectCall(edge, /nsTArray_base.*?::GetAutoArrayBuffer\b/)) &&
isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
{ returntrue;
}
// Watch for the coerced result of a getter_AddRefs or getter_Copies call. if (isDirectCall(edge, /operator /)) { var otherEdge = expressionValueEdge(edge.PEdgeCallInstance.Exp); if (otherEdge &&
isDirectCall(otherEdge, /getter_(?:AddRefs|Copies)/) &&
isEdgeSafeArgument(entry, otherEdge.PEdgeCallArguments.Exp[0]))
{ returntrue;
}
}
// RefPtr::operator->() and operator* transmit the safety of the // RefPtr to the return value. if (isDirectCall(edge, /RefPtr<.*?>::operator(->|\*)\(\)/) &&
isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
{ returntrue;
}
// Placement-new returns a pointer that is as safe as the pointer // passed to it. Exp[0] is the size, Exp[1] is the pointer/address. // Note that the invocation of the constructor is a separate call, // and so need not be considered here. if (isDirectCall(edge, /operator new/) &&
edge.PEdgeCallInstance.Exp.length == 2 &&
isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp[1]))
{ returntrue;
}
// Coercion via AsAString preserves safety. if (isDirectCall(edge, /AsAString/) &&
isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
{ returntrue;
}
// Special case: // // keyframe->mTimingFunction.emplace() // keyframe->mTimingFunction->Init() // // The object calling Init should be considered safe here because // we just emplaced it, though in general keyframe::operator-> // could do something crazy. if (isDirectCall(edge, /operator->/)) do { const predges = getPredecessors(body)[edge.Index[0]]; if (!predges || predges.length != 1) break; const predge = predges[0]; if (!isDirectCall(predge, /\bemplace\b/)) break; const instance = predge.PEdgeCallInstance; if (JSON.stringify(instance) == JSON.stringify(edge.PEdgeCallInstance)) returntrue;
} while (false);
}
if (isSafeAssignment(entry, edge, name)) returntrue;
// Watch out for variables which were assigned arguments. var rhsVariable = variableAssignRhs(edge); if (rhsVariable) return isSafeVariable(entry, rhsVariable);
}
// When temporary stack structures are created (either to return or to call // methods on without assigning them a name), the generated sixgill JSON is // rather strange. The temporary has structure type and is never assigned // to, but is dereferenced. GCC is probably not showing us everything it is // doing to compile this code. Pattern match for this case here.
// The variable should have structure type. var type = null; for (var defvar of currentBody.DefineVariable) { if (variableName(defvar.Variable) == name) {
type = defvar.Type; break;
}
} if (!type || type.Kind != "CSU") returnfalse;
// The variable should not have been written to anywhere up to this point. // If it is initialized at this point we should have seen *some* write // already, since the CFG edges are visited in reverse post order. if (name in assignments) returnfalse;
returntrue;
}
function isSafeMemberPointer(containerType, memberName, memberType)
{ // nsTArray owns its header. if (containerType.includes("nsTArray_base") && memberName == "mHdr") returntrue;
if (memberType.Kind != 'Pointer') returnfalse;
// Special-cases go here :) returnfalse;
}
// Return whether 'exp == value' holds only when execution is on the main thread. function testFailsOffMainThread(exp, value) { switch (exp.Kind) { case"Drf": var edge = expressionValueEdge(exp.Exp[0]); if (edge) { if (isDirectCall(edge, /NS_IsMainThread/) && value) returntrue; if (isDirectCall(edge, /IsInServoTraversal/) && !value) returntrue; if (isDirectCall(edge, /IsCurrentThreadInServoTraversal/) && !value) returntrue; if (isDirectCall(edge, /__builtin_expect/)) return testFailsOffMainThread(edge.PEdgeCallArguments.Exp[0], value); if (edge.Kind == "Assign") return testFailsOffMainThread(edge.Exp[1], value);
} break; case"Unop": if (exp.OpCode == "LogicalNot") return testFailsOffMainThread(exp.Exp[0], !value); break; case"Binop": if (exp.OpCode == "NotEqual" || exp.OpCode == "Equal") { var cmpExp = isZero(exp.Exp[0])
? exp.Exp[1]
: (isZero(exp.Exp[1]) ? exp.Exp[0] : null); if (cmpExp) return testFailsOffMainThread(cmpExp, exp.OpCode == "NotEqual" ? value : !value);
} break; case"Int": if (exp.String == "0" && value) returntrue; if (exp.String == "1" && !value) returntrue; break;
} returnfalse;
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.