DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (b98968eb71d3)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */

this.EXPORTED_SYMBOLS = ["AsanReporter"];

const { XPCOMUtils } = ChromeUtils.import(
  "resource://gre/modules/XPCOMUtils.jsm"
);

XPCOMUtils.defineLazyModuleGetters(this, {
  AppConstants: "resource://gre/modules/AppConstants.jsm",
  Log: "resource://gre/modules/Log.jsm",
  OS: "resource://gre/modules/osfile.jsm",
  Services: "resource://gre/modules/Services.jsm",
});

XPCOMUtils.defineLazyGlobalGetters(this, ["TextDecoder", "XMLHttpRequest"]);

// Define our prefs
const PREF_CLIENT_ID = "asanreporter.clientid";
const PREF_API_URL = "asanreporter.apiurl";
const PREF_AUTH_TOKEN = "asanreporter.authtoken";
const PREF_LOG_LEVEL = "asanreporter.loglevel";

const LOGGER_NAME = "asanreporter";

let logger;

XPCOMUtils.defineLazyGetter(this, "asanDumpDir", () => {
  let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
  return OS.Path.join(profileDir.path, "asan");
});

this.AsanReporter = {
  init() {
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    // Setup logging
    logger = Log.repository.getLogger(LOGGER_NAME);
    logger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
    logger.addAppender(new Log.DumpAppender(new Log.BasicFormatter()));
    logger.level = Services.prefs.getIntPref(PREF_LOG_LEVEL, Log.Level.Info);

    logger.info("Starting up...");

    // Install a handler to observe tab crashes, so we can report those right
    // after they happen instead of relying on the user to restart the browser.
    Services.obs.addObserver(this, "ipc:content-shutdown");

    processDirectory();
  },

  observe(aSubject, aTopic, aData) {
    if (aTopic == "ipc:content-shutdown") {
      aSubject.QueryInterface(Ci.nsIPropertyBag2);
      if (!aSubject.get("abnormal")) {
        return;
      }
      processDirectory();
    }
  },
};

function processDirectory() {
  let iterator = new OS.File.DirectoryIterator(asanDumpDir);
  let results = [];

  // Scan the directory for any ASan logs that we haven't
  // submitted yet. Store the filenames in an array so we
  // can close the iterator early.
  iterator
    .forEach(entry => {
      if (
        entry.name.indexOf("ff_asan_log.") == 0 &&
        !entry.name.includes("submitted")
      ) {
        results.push(entry);
      }
    })
    .then(
      () => {
        iterator.close();
        logger.info("Processing " + results.length + " reports...");

        // Sequentially submit all reports that we found. Note that doing this
        // with Promise.all would not result in a sequential ordering and would
        // cause multiple requests to be sent to the server at once.
        let requests = Promise.resolve();
        results.forEach(result => {
          requests = requests.then(
            // We return a promise here that already handles any submit failures
            // so our chain is not interrupted if one of the reports couldn't
            // be submitted for some reason.
            () =>
              submitReport(result.path).then(
                () => {
                  logger.info("Successfully submitted " + result.path);
                },
                e => {
                  logger.error(
                    "Failed to submit " + result.path + ". Reason: " + e
                  );
                }
              )
          );
        });

        requests.then(() => logger.info("Done processing reports."));
      },
      e => {
        iterator.close();
        logger.error("Error while iterating over report files: " + e);
      }
    );
}

function submitReport(reportFile) {
  logger.info("Processing " + reportFile);
  return OS.File.read(reportFile)
    .then(submitToServer)
    .then(() => {
      // Mark as submitted only if we successfully submitted it to the server.
      return OS.File.move(reportFile, reportFile + ".submitted");
    });
}

function submitToServer(data) {
  return new Promise(function(resolve, reject) {
    logger.debug("Setting up XHR request");
    let client = Services.prefs.getStringPref(PREF_CLIENT_ID);
    let api_url = Services.prefs.getStringPref(PREF_API_URL);
    let auth_token = Services.prefs.getStringPref(PREF_AUTH_TOKEN, null);

    let decoder = new TextDecoder();

    if (!client) {
      client = "unknown";
    }

    let versionArr = [
      Services.appinfo.version,
      Services.appinfo.appBuildID,
      AppConstants.SOURCE_REVISION_URL || "unknown",
    ];

    // Concatenate all relevant information as our server only
    // has one field available for version information.
    let product_version = versionArr.join("-");
    let os = AppConstants.platform;

    let reportObj = {
      rawStdout: "",
      rawStderr: "",
      rawCrashData: decoder.decode(data),
      // Hardcode platform as there is no other reasonable platform for ASan
      platform: "x86-64",
      product: "mozilla-central-asan-nightly",
      product_version,
      os,
      client,
      tool: "asan-nightly-program",
    };

    var xhr = new XMLHttpRequest();
    xhr.open("POST", api_url, true);
    xhr.setRequestHeader("Content-Type", "application/json");

    // For internal testing purposes, an auth_token can be specified
    if (auth_token) {
      xhr.setRequestHeader("Authorization", "Token " + auth_token);
    } else {
      // Prevent privacy leaks
      xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS;
    }

    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4) {
        if (xhr.status == "201") {
          logger.debug("XHR: OK");
          resolve(xhr);
        } else {
          logger.debug(
            "XHR: Status: " + xhr.status + " Response: " + xhr.responseText
          );
          reject(xhr);
        }
      }
    };

    xhr.send(JSON.stringify(reportObj));
  });
}