Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  analyzeHeapWrites.js   Sprache: JAVA

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */


/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */

"use strict";

loadRelativeToScript('utility.js');
loadRelativeToScript('annotations.js');
loadRelativeToScript('callgraph.js');
loadRelativeToScript('dumpCFG.js');

///////////////////////////////////////////////////////////////////////////////
// Annotations
///////////////////////////////////////////////////////////////////////////////

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;

    dumpError(entry, location, "Indirect call " + callee);
}

function checkVariableAssignment(entry, location, variable)
{
    var name = entry.name;

    dumpError(entry, location, "Variable assignment " + variable);
}

// 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.
        [nullnull, /StyleChildrenIterator/],
        [nullnull, /ExplicitChildIterator/],

        // The use of BeginReading() to instantiate this class confuses the
        // analysis.
        [nullnull, /nsReadingIterator/],

        // These classes are passed to some Servo bindings to fill in.
        [/^Gecko_/, null"nsStyleImageLayers"],
        [/^Gecko_/, null, /FontFamilyList/],

        // Various Servo binding out parameters. This is a mess and there needs
        // to be a way to indicate which params are out parameters, either using
        // an attribute or a naming convention.
        [/ClassOrClassList/, /aClass/, null],
        ["Gecko_GetAtomAsUTF16""aLength"null],
        ["Gecko_CopyMozBindingFrom""aDest"null],
        ["Gecko_SetNullImageValue""aImage"null],
        ["Gecko_SetGradientImageValue""aImage"null],
        ["Gecko_SetImageElement""aImage"null],
        ["Gecko_SetLayerImageImageValue""aImage"null],
        ["Gecko_CopyImageValueFrom""aImage"null],
        ["Gecko_SetCursorArrayLength""aStyleUI"null],
        ["Gecko_CopyCursorArrayFrom""aDest"null],
        ["Gecko_SetCursorImageValue""aCursor"null],
        ["Gecko_SetListStyleImageImageValue""aList"null],
        ["Gecko_SetListStyleImageNone""aList"null],
        ["Gecko_CopyListStyleImageFrom""aList"null],
        ["Gecko_ClearStyleContents""aContent"null],
        ["Gecko_CopyStyleContentsFrom""aContent"null],
        ["Gecko_CopyStyleGridTemplateValues""aGridTemplate"null],
        ["Gecko_ResetStyleCoord"nullnull],
        ["Gecko_CopyClipPathValueFrom""aDst"null],
        ["Gecko_DestroyClipPath""aClip"null],
        ["Gecko_ResetFilters""effects"null],
        [/Gecko_CSSValue_Set/, "aCSSValue"null],
        ["Gecko_CSSValue_Drop""aCSSValue"null],
        ["Gecko_CSSFontFaceRule_GetCssText""aResult"null],
        ["Gecko_EnsureTArrayCapacity""aArray"null],
        ["Gecko_ClearPODTArray""aArray"null],
        ["Gecko_SetStyleGridTemplate""aGridTemplate"null],
        ["Gecko_ResizeTArrayForStrings""aArray"null],
        ["Gecko_ClearAndResizeStyleContents""aContent"null],
        [/Gecko_ClearAndResizeCounter/, "aContent"null],
        [/Gecko_CopyCounter.*?From/, "aContent"null],
        [/Gecko_SetContentDataImageValue/, "aList"null],
        [/Gecko_SetContentData/, "aContent"null],
        ["Gecko_SetCounterFunction""aContent"null],
        [/Gecko_EnsureStyle.*?ArrayLength/, "aArray"null],
        ["Gecko_GetOrCreateKeyframeAtStart""aKeyframes"null],
        ["Gecko_GetOrCreateInitialKeyframe""aKeyframes"null],
        ["Gecko_GetOrCreateFinalKeyframe""aKeyframes"null],
        ["Gecko_AppendPropertyValuePair""aProperties"null],
        ["Gecko_SetStyleCoordCalcValue"nullnull],
        ["Gecko_StyleClipPath_SetURLValue""aClip"null],
        ["Gecko_nsStyleFilter_SetURLValue""aEffects"null],
        ["Gecko_nsStyleSVG_SetDashArrayLength""aSvg"null],
        ["Gecko_nsStyleSVG_CopyDashArray""aDst"null],
        ["Gecko_nsStyleFont_SetLang""aFont"null],
        ["Gecko_nsStyleFont_CopyLangFrom""aFont"null],
        ["Gecko_ClearWillChange""aDisplay"null],
        ["Gecko_AppendWillChange""aDisplay"null],
        ["Gecko_CopyWillChangeFrom""aDest"null],
        ["Gecko_InitializeImageCropRect""aImage"null],
        ["Gecko_CopyShapeSourceFrom""aDst"null],
        ["Gecko_DestroyShapeSource""aShape"null],
        ["Gecko_StyleShapeSource_SetURLValue""aShape"null],
        ["Gecko_NewBasicShape""aShape"null],
        ["Gecko_NewShapeImage""aShape"null],
        ["Gecko_nsFont_InitSystem""aDest"null],
        ["Gecko_nsFont_SetFontFeatureValuesLookup""aFont"null],
        ["Gecko_nsFont_ResetFontFeatureValuesLookup""aFont"null],
        ["Gecko_nsStyleFont_FixupNoneGeneric""aFont"null],
        ["Gecko_StyleTransition_SetUnsupportedProperty""aTransition"null],
        ["Gecko_AddPropertyToSet""aPropertySet"null],
        ["Gecko_CalcStyleDifference""aAnyStyleChanged"null],
        ["Gecko_CalcStyleDifference""aOnlyResetStructsChanged"null],
        ["Gecko_nsStyleSVG_CopyContextProperties""aDst"null],
        ["Gecko_nsStyleFont_PrefillDefaultForGeneric""aFont"null],
        ["Gecko_nsStyleSVG_SetContextPropertiesLength""aSvg"null],
        ["Gecko_ClearAlternateValues""aFont"null],
        ["Gecko_AppendAlternateValues""aFont"null],
        ["Gecko_CopyAlternateValuesFrom""aDest"null],
        ["Gecko_nsTArray_FontFamilyName_AppendNamed""aNames"null],
        ["Gecko_nsTArray_FontFamilyName_AppendGeneric""aNames"null],
    ];
    for (var [entryMatch, varMatch, csuMatch] of whitelist) {
        assert(entryMatch || varMatch || csuMatch);
        if (entryMatch && !nameMatches(entry.name, entryMatch))
            continue;
        if (varMatch && !nameMatches(varName, varMatch))
            continue;
        if (csuMatch && (!csuName || !nameMatches(csuName, csuMatch)))
            continue;
        return true;
    }
    return false;
}

function isSafeAssignment(entry, edge, variable)
{
    if (edge.Kind != 'Assign')
        return false;

    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'
        }
    }

    return false;
}

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;

    dumpError(entry, location, "Field write" + str);
}

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;

    dumpError(entry, location, "Dereference write " + (variable ? variable : ""));
}

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))
    {
        return true;
    }

    // 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))
    {
        return true;
    }

    // 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))
    {
        return true;
    }

    // StyleShapeSource exclusively owns its UniquePtr<nsStyleImage>.
    if (/nsStyleImage::SetURLValue/.test(callee) &&
        /StyleShapeSource::SetURL/.test(name) &&
        entry.isSafeArgument(0))
    {
        return true;
    }

    // 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))
    {
        return true;
    }

    // AllChildrenIterator asks AppendOwnedAnonBoxes to append into an nsTArray
    // local variable.
    if (/nsIFrame::AppendOwnedAnonBoxes/.test(callee) &&
        /AllChildrenIterator::AppendNativeAnonymousChildren/.test(name))
    {
        return true;
    }

    // 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))
    {
        return true;
    }

    // We manually lock here
    if (name == "Gecko_nsFont_InitSystem" ||
        name == "Gecko_GetFontMetrics" ||
        name == "Gecko_nsStyleFont_FixupMinFontSize" ||
        /ThreadSafeGetDefaultFontHelper/.test(name))
    {
        return true;
    }

    return false;
}

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 ought to be threadsafe.
        "NS_DebugBreak",
        /mozalloc_handle_oom/,
        /^NS_Log/, /log_print/, /LazyLogModule::operator/,
        /SprintfLiteral/, "PR_smprintf""PR_smprintf_free",
        /NS_DispatchToMainThread/, /NS_ReleaseOnMainThread/,
        /NS_NewRunnableFunction/, /NS_Atomize/,
        /nsCSSValue::BufferFromString/,
        /NS_xstrdup/,
        /Assert_NoQueryNeeded/,
        /AssertCurrentThreadOwnsMe/,
        /PlatformThread::CurrentId/,
        /imgRequestProxy::GetProgressTracker/, // Uses an AutoLock
        /Smprintf/,
        "malloc",
        "calloc",
        "free",
        "realloc",
        "memalign",
        "strdup",
        "strndup",
        "moz_xmalloc",
        "moz_xcalloc",
        "moz_xrealloc",
        "moz_xmemalign",
        "moz_xstrdup",
        "moz_xstrndup",
        "jemalloc_thread_local_arena",

        // 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))
        return true;

    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))
            return true;
    }

    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))
            return true;
    }

    if (entry.isSafeArgument(2)) {
        var secondArgWhitelist = [
            /StringBuffer::ToString/,
            /AppendUTF\d+toUTF\d+/,
            /AppendASCIItoUTF\d+/,
        ];
        if (entry.matches(secondArgWhitelist))
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////
// Sixgill Utilities
///////////////////////////////////////////////////////////////////////////////

function variableName(variable)
{
    return (variable && variable.Name) ? variable.Name[0] : null;
}

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];
}

function isLocalVariable(variable)
{
    switch (variable.Kind) {
      case "Return":
      case "Temp":
      case "Local":
      case "Arg":
        return true;
    }
    return false;
}

function isDirectCall(edge, regexp)
{
    return edge.Kind == "Call"
        && edge.Exp[0].Kind == "Var"
        && regexp.test(variableName(edge.Exp[0].Variable));
}

function isZero(exp)
{
    return exp.Kind == "Int" && exp.String == "0";
}

///////////////////////////////////////////////////////////////////////////////
// Analysis Structures
///////////////////////////////////////////////////////////////////////////////

// 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.

function WorklistEntry(name, safeArguments, stack, parameterNames)
{
    this.name = name;
    this.safeArguments = safeArguments;
    this.stack = stack;
    this.parameterNames = parameterNames;
}

WorklistEntry.prototype.readable = function()
{
    const [ mangled, readable ] = splitFunction(this.name);
    return readable;
}

WorklistEntry.prototype.mangledName = function()
{
    var str = this.name;
    for (var safe of this.safeArguments)
        str += " SAFE " + safe;
    return str;
}

WorklistEntry.prototype.isSafeArgument = function(index)
{
    for (var safe of this.safeArguments) {
        if (index == safe)
            return true;
    }
    return false;
}

WorklistEntry.prototype.setParameterName = function(index, name)
{
    this.parameterNames[index] = name;
}

WorklistEntry.prototype.addSafeArgument = function(index)
{
    if (this.isSafeArgument(index))
        return;
    this.safeArguments.push(index);

    // Sorting isn't necessary for correctness but makes printed stack info tidier.
    this.safeArguments.sort();
}

function safeArgumentIndex(variable)
{
    if (variable.Kind == "This")
        return 0;
    if (variable.Kind == "Arg")
        return variable.Index + 1;
    return -1;
}

function nameMatches(name, match)
{
    if (typeof match == "string") {
        if (name == match)
            return true;
    } else {
        assert(match instanceof RegExp);
        if (match.test(name))
            return true;
    }
    return false;
}

function nameMatchesArray(name, matchArray)
{
    for (var match of matchArray) {
        if (nameMatches(name, match))
            return true;
    }
    return false;
}

WorklistEntry.prototype.matches = function(matchArray)
{
    return nameMatchesArray(this.name, matchArray);
}

function CallSite(callee, safeArguments, location, parameterNames)
{
    this.callee = callee;
    this.safeArguments = safeArguments;
    this.location = location;
    this.parameterNames = parameterNames;
}

CallSite.prototype.safeString = function()
{
    if (this.safeArguments.length) {
        var str = "";
        for (var i = 0; i < this.safeArguments.length; i++) {
            var arg = this.safeArguments[i];
            if (arg in this.parameterNames)
                str += " " + this.parameterNames[arg];
            else
                str += " <" + ((arg == 0) ? "this" : "arg" + (arg - 1)) + ">";
        }
        return " ### SafeArguments:" + str;
    }
    return "";
}

///////////////////////////////////////////////////////////////////////////////
// Analysis Core
///////////////////////////////////////////////////////////////////////////////

var errorCount = 0;
var errorLimit = 100;

// 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 options = parse_options([
    {
        name: '--strip-prefix',
        default: os.getenv('SOURCE') || '',
        type: 'string'
    },
    {
        name: '--add-prefix',
        default: os.getenv('URLPREFIX') || '',
        type: 'string'
    },
    {
        name: '--verbose',
        type: 'bool'
    },
]);

function add_trailing_slash(str) {
    if (str == '')
        return str;
    return str.endsWith("/") ? str : str + "/";
}

var removePrefix = add_trailing_slash(options.strip_prefix);
var addPrefix = add_trailing_slash(options.add_prefix);

if (options.verbose) {
    printErr(`Removing prefix ${removePrefix} from paths`);
    printErr(`Prepending ${addPrefix} to paths`);
}

print(elapsedTime() + "Loading types...");
if (os.getenv("TYPECACHE"))
    loadTypesWithCache('src_comp.xdb', os.getenv("TYPECACHE"));
else
    loadTypes('src_comp.xdb');
print(elapsedTime() + "Starting analysis...");

var xdb = xdbLibrary();
xdb.open("src_body.xdb");

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;
    }
    return null;
}

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;
            }

            if (!(name in assignments))
                assignments[name] = [];
            assignments[name].push(assign);
        } else {
            checkVariableAssignment(entry, location, name);
        }
        return;
      case "Drf":
        var variable = null;
        if (lhs.Exp[0].Kind == "Var") {
            variable = lhs.Exp[0].Variable;
            if (isSafeVariable(entry, variable))
                return;
        } else if (lhs.Exp[0].Kind == "Fld") {
            const {
                Name: [ fieldName ],
                Type: {Kind, Type: fieldType},
                FieldCSU: {Type: {Kind: containerTypeKind,
                                  Name: containerTypeName}}
            } = lhs.Exp[0].Field;
            const [containerExpr] = lhs.Exp[0].Exp;

            if (containerTypeKind == 'CSU' &&
                Kind == 'Pointer' &&
                isEdgeSafeArgument(entry, containerExpr) &&
                isSafeMemberPointer(containerTypeName, fieldName, fieldType))
            {
                return;
            }
        }
        if (fields.length)
            checkFieldWrite(entry, location, fields);
        else
            checkDereferenceWrite(entry, location, variableName(variable));
        return;
      case "Int":
        if (isZero(lhs)) {
            // This shows up under MOZ_ASSERT, to crash the process.
            return;
        }
    }
    dumpError(entry, location, "Unknown assignment " + JSON.stringify(lhs));
}

function get_location(rawLocation) {
    const filename = rawLocation.CacheString.replace(removePrefix, '');
    return addPrefix + filename + "#" + rawLocation.Line;
}

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;
            }
        }

        var fallthrough = true;

        if (edge.Kind == "Assign") {
            assert(edge.Exp.length == 2);
            processAssign(body, entry, location, edge.Exp[0], edge);
        } else if (edge.Kind == "Call") {
            assert(edge.Exp.length <= 2);
            if (edge.Exp.length == 2)
                processAssign(body, entry, location, edge.Exp[1], edge);

            // 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;
        } else if (edge.Kind == "Loop") {
            reachableLoops[edge.BlockId.Loop] = true;
        } else if (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));
        return true;
    }

    // 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));
        return true;
    }

    // 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));
        return true;
    }

    // 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));
        return true;
    }

    return false;
}

function processRoot(name)
{
    var safeArguments = [];
    var parameterNames = {};
    var worklist = [new WorklistEntry(name, safeArguments, [new CallSite(name, safeArgumentsnull, 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))
        return true;
    if (exp.Kind == "Drf" && exp.Exp[0].Kind == "Var") {
        var variable = exp.Exp[0].Variable;
        return isSafeVariable(entry, variable);
    }
    if (isZero(exp))
        return true;
    return false;
}

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];
    }
    return null;
}

function expressionValueEdge(exp) {
    if (!(exp.Kind == "Var" && exp.Variable.Kind == "Temp"))
        return null;
    const assign = singleAssignment(variableName(exp.Variable));
    if (!assign)
        return null;
    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")
        return false;
    var name = variableName(variable);

    if (!entry.safeLocals)
        entry.safeLocals = new Map;
    if (entry.safeLocals.has(name))
        return entry.safeLocals.get(name);

    const safe = isSafeLocalVariable(entry, name);
    entry.safeLocals.set(name, safe);
    return safe;
}

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/))
            return true;

        // 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/))
        {
            return true;
        }

        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))
            {
                return true;
            }

            // 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]))
                {
                    return true;
                }
            }

            // RefPtr::operator->() and operator* transmit the safety of the
            // RefPtr to the return value.
            if (isDirectCall(edge, /RefPtr<.*?>::operator(->|\*)\(\)/) &&
                isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
            {
                return true;
            }

            // 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]))
            {
                return true;
            }

            // Coercion via AsAString preserves safety.
            if (isDirectCall(edge, /AsAString/) &&
                isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
            {
                return true;
            }

            // 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))
                    return true;
            } while (false);
        }

        if (isSafeAssignment(entry, edge, name))
            return true;

        // 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")
        return false;

    // 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)
        return false;

    return true;
}

function isSafeMemberPointer(containerType, memberName, memberType)
{
    // nsTArray owns its header.
    if (containerType.includes("nsTArray_base") && memberName == "mHdr")
        return true;

    if (memberType.Kind != 'Pointer')
        return false;

    // Special-cases go here :)
    return false;
}

// 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)
                return true;
            if (isDirectCall(edge, /IsInServoTraversal/) && !value)
                return true;
            if (isDirectCall(edge, /IsCurrentThreadInServoTraversal/) && !value)
                return true;
            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)
            return true;
        if (exp.String == "1" && !value)
            return true;
        break;
    }
    return false;
}

Messung V0.5
C=86 H=91 G=88

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge