Exemple #1
0
def interesting(cli_args, temp_prefix):  # pylint: disable=missing-docstring,missing-return-doc,missing-return-type-doc
    (regex_enabled, crash_sig, timeout, args) = parse_options(cli_args)

    # Examine stack for crash signature, this is needed if crash_sig is specified.
    runinfo = timed_run.timed_run(args, timeout, temp_prefix)
    if runinfo.sta == timed_run.CRASHED:
        sps.grabCrashLog(args[0], runinfo.pid, temp_prefix, True)

    time_str = " (%.3f seconds)" % runinfo.elapsedtime

    crash_log = temp_prefix + "-crash.txt"

    if runinfo.sta == timed_run.CRASHED:
        if os.path.exists(crash_log):
            # When using this script, remember to escape characters, e.g. "\(" instead of "(" !
            found, _found_sig = file_contains(crash_log, crash_sig,
                                              regex_enabled)
            if found:
                print("Exit status: %s%s" % (runinfo.msg, time_str))
                return True
            print("[Uninteresting] It crashed somewhere else!" + time_str)
            return False
        print(
            "[Uninteresting] It appeared to crash, but no crash log was found?"
            + time_str)
        return False
    print("[Uninteresting] It didn't crash." + time_str)
    return False
def interesting(cliArgs, tempPrefix):
    (regexEnabled, crashSig, timeout, args) = parseOptions(cliArgs)

    # Examine stack for crash signature, this is needed if crashSig is specified.
    runinfo = timed_run.timed_run(args, timeout, tempPrefix)
    if runinfo.sta == timed_run.CRASHED:
        sps.grabCrashLog(args[0], runinfo.pid, tempPrefix, True)

    timeString = " (%.3f seconds)" % runinfo.elapsedtime

    crashLogName = tempPrefix + "-crash.txt"

    if runinfo.sta == timed_run.CRASHED:
        if os.path.exists(crashLogName):
            # When using this script, remember to escape characters, e.g. "\(" instead of "(" !
            found, _foundSig = utils.file_contains(crashLogName, crashSig,
                                                   regexEnabled)
            if found:
                print 'Exit status: ' + runinfo.msg + timeString
                return True
            else:
                print "[Uninteresting] It crashed somewhere else!" + timeString
                return False
        else:
            print "[Uninteresting] It appeared to crash, but no crash log was found?" + timeString
            return False
    else:
        print "[Uninteresting] It didn't crash." + timeString
        return False
def interesting(args, tempPrefix):
    timeout = int(args[0])
    returncode = subprocess.call([jsshell, "-c", args[1]],
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
    if returncode != 0:
        print "JS didn't compile, skipping browser test"
        return False
    wantStack = False  # We do not care about the stack when using this interestingness test.
    runinfo = timed_run.timed_run(args[2:], timeout, tempPrefix, wantStack)
    print "Exit status: %s (%.3f seconds)" % (runinfo.msg, runinfo.elapsedtime)
    return runinfo.sta == timed_run.CRASHED or runinfo.sta == timed_run.ABNORMAL
    def __init__(self, options, runthis, logPrefix, in_compare_jit, env=None):  # pylint: disable=too-complex
        # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements

        # If Lithium uses this as an interestingness test, logPrefix is likely not a Path object, so make it one.
        logPrefix = Path(logPrefix)
        pathToBinary = runthis[0].expanduser().resolve()  # pylint: disable=invalid-name
        # This relies on the shell being a local one from compile_shell:
        # Ignore trailing ".exe" in Win, also abspath makes it work w/relative paths like "./js"
        # pylint: disable=invalid-name
        assert pathToBinary.with_suffix(".fuzzmanagerconf").is_file()
        pc = ProgramConfiguration.fromBinary(
            str(pathToBinary.parent / pathToBinary.stem))
        pc.addProgramArguments(runthis[1:-1])

        if options.valgrind:
            runthis = (inspect_shell.constructVgCmdList(
                errorCode=VALGRIND_ERROR_EXIT_CODE) + [
                    f"--suppressions={filename}"
                    for filename in "valgrind_suppressions.txt"
                ] + runthis)

        timed_run_kw = {"env": (env or deepcopy(os.environ))}

        # Enable LSan which is enabled with non-ARM64 simulator ASan, only on Linux
        if platform.system(
        ) == "Linux" and inspect_shell.queryBuildConfiguration(
                options.jsengine, "asan"):
            env_asan_options = "detect_leaks=1,"
            env_lsan_options = "max_leaks=1,"
            if inspect_shell.queryBuildConfiguration(options.jsengine,
                                                     "arm64-simulator"):
                env_asan_options = ""
                env_lsan_options = ""
            timed_run_kw["env"].update({"ASAN_OPTIONS": env_asan_options})
            timed_run_kw["env"].update({"LSAN_OPTIONS": env_lsan_options})
        elif not platform.system() == "Windows":
            timed_run_kw["preexec_fn"] = set_ulimit

        pc.addEnvironmentVariables(dict(timed_run_kw["env"]))

        lithium_logPrefix = str(logPrefix).encode("utf-8")
        if isinstance(lithium_logPrefix, b"".__class__):
            lithium_logPrefix = lithium_logPrefix.decode("utf-8",
                                                         errors="replace")

        # logPrefix should be a string for timed_run in Lithium version 0.2.1 to work properly, apparently
        runinfo = timedrun.timed_run(
            [str(x) for x in runthis
             ],  # Convert all Paths/bytes to strings for Lithium
            options.timeout,
            lithium_logPrefix,
            **timed_run_kw)

        lev = JS_FINE
        is_oom = False
        issues = []
        auxCrashData = []  # pylint: disable=invalid-name

        # FuzzManager expects a list of strings rather than an iterable, so bite the
        # bullet and "readlines" everything into memory.
        # Collector adds newlines later, see https://git.io/fjoMB
        out_log = (logPrefix.parent /
                   f"{logPrefix.stem}-out").with_suffix(".txt")
        with io.open(str(out_log), "r", encoding="utf-8",
                     errors="replace") as f:
            out = [line.rstrip() for line in f]
        err_log = (logPrefix.parent /
                   f"{logPrefix.stem}-err").with_suffix(".txt")
        with io.open(str(err_log), "r", encoding="utf-8",
                     errors="replace") as f:
            err = [line.rstrip() for line in f]

        for line in reversed(err):
            if "[unhandlable oom]" in line:
                print("Ignoring unhandlable oom...")
                is_oom = True
                break

        if is_oom:
            lev = JS_FINE
            crash_log = (logPrefix.parent /
                         f"{logPrefix.stem}-crash").with_suffix(".txt")
            core_file = logPrefix.parent / f"{logPrefix.stem}-core"
            if crash_log.is_file():
                crash_log.unlink()
            if core_file.is_file():
                core_file.unlink()

            dbggr_cmd = os_ops.make_dbg_cmd(runthis[0], runinfo.pid)
            if dbggr_cmd:
                core_file = Path(dbggr_cmd[-1])
                if core_file.is_file():
                    core_file.unlink()
        elif options.valgrind and runinfo.return_code == VALGRIND_ERROR_EXIT_CODE:
            issues.append("valgrind reported an error")
            lev = max(lev, JS_VG_AMISS)
            valgrindErrorPrefix = f"=={runinfo.pid}=="
            for line in err:
                if valgrindErrorPrefix and line.startswith(
                        valgrindErrorPrefix):
                    issues.append(line.rstrip())
        elif runinfo.sta == timedrun.CRASHED:
            if os_ops.grab_crash_log(runthis[0], runinfo.pid, logPrefix, True):
                crash_log = (logPrefix.parent /
                             f"{logPrefix.stem}-crash").with_suffix(".txt")
                with io.open(str(crash_log),
                             "r",
                             encoding="utf-8",
                             errors="replace") as f:
                    auxCrashData = [line.strip() for line in f.readlines()]
        elif file_manipulation.amiss(logPrefix):
            issues.append("malloc error")
            lev = max(lev, JS_NEW_ASSERT_OR_CRASH)
        elif runinfo.return_code == 0 and not in_compare_jit:
            # We might have(??) run jsfunfuzz directly, so check for special kinds of bugs
            for line in out:
                if line.startswith("Found a bug: ") and not ("NestTest" in line
                                                             and oomed(err)):
                    lev = JS_DECIDED_TO_EXIT
                    issues.append(line.rstrip())
            if options.shellIsDeterministic and not understoodJsfunfuzzExit(
                    out, err) and not oomed(err):
                issues.append("jsfunfuzz didn't finish")
                lev = JS_DID_NOT_FINISH

        # Copy non-crash issues to where FuzzManager's "AssertionHelper" can see it.
        if lev != JS_FINE:
            for issue in issues:
                err.append(f"[Non-crash bug] {issue}")

        activated = False  # Turn on when trying to report *reliable* testcases that do not have a coredump
        # On Linux, fall back to run testcase via gdb using --args if core file data is unavailable
        # Note that this second round of running uses a different fuzzSeed as the initial if default jsfunfuzz is run
        # We should separate this out, i.e. running jsfunfuzz within a debugger, only if core dumps cannot be generated
        if (activated and platform.system() == "Linux" and shutil.which("gdb")
                and not auxCrashData and not in_compare_jit):
            print(
                "Note: No core file found on Linux - falling back to run via gdb"
            )
            extracted_gdb_cmds = ["-ex", "run"]
            with io.open(str(
                    Path(__file__).parent.parent / "util" / "gdb_cmds.txt"),
                         "r",
                         encoding="utf-8",
                         errors="replace") as f:
                for line in f:
                    if line.rstrip() and not line.startswith(
                            "#") and not line.startswith("echo"):
                        extracted_gdb_cmds.append("-ex")
                        extracted_gdb_cmds.append(f"{line.rstrip()}")
            no_main_log_gdb_log = subprocess.run(
                (["gdb", "-n", "-batch"] + extracted_gdb_cmds + ["--args"] +
                 [str(x) for x in runthis]),
                check=True,
                stderr=subprocess.PIPE,
                stdout=subprocess.PIPE,
            )
            auxCrashData = no_main_log_gdb_log.stdout

        # Finally, make a CrashInfo object and parse stack traces for asan/crash/assertion bugs
        crashInfo = Crash_Info.CrashInfo.fromRawCrashData(
            out, err, pc, auxCrashData=auxCrashData)

        create_collector.printCrashInfo(crashInfo)
        # We only care about crashes and assertion failures on shells with no symbols
        # Note that looking out for the Assertion failure message is highly SpiderMonkey-specific
        if not is_oom and (not isinstance(crashInfo, Crash_Info.NoCrashInfo)
                           or "Assertion failure: " in str(crashInfo.rawStderr)
                           or "Segmentation fault" in str(crashInfo.rawStderr)
                           or "Bus error" in str(crashInfo.rawStderr)):
            lev = max(lev, JS_NEW_ASSERT_OR_CRASH)

        try:
            match = options.collector.search(crashInfo)
            if match[0] is not None:
                create_collector.printMatchingSignature(match)
                if match[1].get("frequent"):
                    print("Ignoring frequent bucket")
                    lev = JS_FINE
        except UnicodeDecodeError:  # Sometimes FM throws due to unicode issues
            print(
                "Note: FuzzManager is throwing a UnicodeDecodeError, signature matching skipped"
            )
            match = False

        print(
            f"{logPrefix} | {summaryString(issues, lev, runinfo.elapsedtime)}")

        if lev != JS_FINE:
            summary_log = (logPrefix.parent /
                           f"{logPrefix.stem}-summary").with_suffix(".txt")
            with io.open(str(summary_log),
                         "w",
                         encoding="utf-8",
                         errors="replace") as f:
                f.writelines([
                    f"Number: {logPrefix}\n",
                    f'Command: {" ".join(quote(str(x)) for x in runthis)}\n'
                ] + [f"Status: {i}\n" for i in issues])

        self.lev = lev
        self.out = out
        self.err = err
        self.issues = issues
        self.crashInfo = crashInfo  # pylint: disable=invalid-name
        self.match = match
        self.runinfo = runinfo
        self.return_code = runinfo.return_code
Exemple #5
0
def interesting(cli_args, temp_prefix):
    """Interesting if the binary crashes with a possibly-desired signature on the stack.

    Args:
        cli_args (list): List of input arguments.
        temp_prefix (str): Temporary directory prefix, e.g. tmp1/1 or tmp4/1

    Returns:
        bool: True if the intended signature shows up on the stack, False otherwise.
    """
    parser = argparse.ArgumentParser(
        prog="crashesat",
        usage=
        "python3 -m lithium %(prog)s [options] binary [flags] testcase.ext")
    parser.add_argument(
        "-r",
        "--regex",
        action="store_true",
        default=False,
        help="Allow search for regular expressions instead of strings.")
    parser.add_argument(
        "-s",
        "--sig",
        default="",
        type=str,
        help="Match this crash signature. Defaults to '%default'.")
    parser.add_argument(
        "-t",
        "--timeout",
        default=120,
        type=int,
        help="Optionally set the timeout. Defaults to '%default' seconds.")
    parser.add_argument("cmd_with_flags", nargs=argparse.REMAINDER)
    args = parser.parse_args(cli_args)

    log = logging.getLogger(__name__)

    # Examine stack for crash signature, this is needed if args.sig is specified.
    runinfo = timedrun.timed_run(args.cmd_with_flags, args.timeout,
                                 temp_prefix)
    if runinfo.sta == timedrun.CRASHED:
        os_ops.grab_crash_log(Path(args.cmd_with_flags[0]), runinfo.pid,
                              Path(temp_prefix), True)

    crash_log = Path(f"{temp_prefix}-crash.txt")
    time_str = f" ({runinfo.elapsedtime:.3f} seconds)"

    if runinfo.sta == timedrun.CRASHED:
        if crash_log.resolve().is_file():
            # When using this script, remember to escape characters, e.g. "\(" instead of "(" !
            if file_contains(str(crash_log), args.sig.encode("utf-8"),
                             args.regex)[0]:
                log.info("Exit status: %s%s", runinfo.msg, time_str)
                return True
            log.info("[Uninteresting] It crashed somewhere else!%s", time_str)
            return False
        log.info(
            "[Uninteresting] It appeared to crash, but no crash log was found?%s",
            time_str)
        return False
    log.info("[Uninteresting] It didn't crash.%s", time_str)
    return False
Exemple #6
0
    def __init__(self, options, runthis, logPrefix, inCompareJIT):
        pathToBinary = runthis[0]
        # This relies on the shell being a local one from compileShell.py:
        # Ignore trailing ".exe" in Win, also abspath makes it work w/relative paths like './js'
        pc = ProgramConfiguration.fromBinary(
            os.path.abspath(pathToBinary).split('.')[0])
        pc.addProgramArguments(runthis[1:-1])

        if options.valgrind:
            runthis = (inspectShell.constructVgCmdList(
                errorCode=VALGRIND_ERROR_EXIT_CODE) +
                       valgrindSuppressions(options.knownPath) + runthis)

        preexec_fn = ulimitSet if os.name == 'posix' else None
        runinfo = timed_run.timed_run(runthis,
                                      options.timeout,
                                      logPrefix,
                                      preexec_fn=preexec_fn)

        lev = JS_FINE
        issues = []
        auxCrashData = []

        # FuzzManager expects a list of strings rather than an iterable, so bite the
        # bullet and 'readlines' everything into memory.
        with open(logPrefix + "-out.txt") as f:
            out = f.readlines()
        with open(logPrefix + "-err.txt") as f:
            err = f.readlines()

        if options.valgrind and runinfo.return_code == VALGRIND_ERROR_EXIT_CODE:
            issues.append("valgrind reported an error")
            lev = max(lev, JS_VG_AMISS)
            valgrindErrorPrefix = "==" + str(runinfo.pid) + "=="
            for line in err:
                if valgrindErrorPrefix and line.startswith(
                        valgrindErrorPrefix):
                    issues.append(line.rstrip())
        elif runinfo.sta == timed_run.CRASHED:
            if sps.grabCrashLog(runthis[0], runinfo.pid, logPrefix, True):
                with open(logPrefix + "-crash.txt") as f:
                    auxCrashData = [line.strip() for line in f.readlines()]
        elif detect_malloc_errors.amiss(logPrefix):
            issues.append("malloc error")
            lev = max(lev, JS_NEW_ASSERT_OR_CRASH)
        elif runinfo.return_code == 0 and not inCompareJIT:
            # We might have(??) run jsfunfuzz directly, so check for special kinds of bugs
            for line in out:
                if line.startswith("Found a bug: ") and not ("NestTest" in line
                                                             and oomed(err)):
                    lev = JS_DECIDED_TO_EXIT
                    issues.append(line.rstrip())
            if options.shellIsDeterministic and not understoodJsfunfuzzExit(
                    out, err) and not oomed(err):
                issues.append("jsfunfuzz didn't finish")
                lev = JS_DID_NOT_FINISH

        # Copy non-crash issues to where FuzzManager's "AssertionHelper.py" can see it.
        if lev != JS_FINE:
            for issue in issues:
                err.append("[Non-crash bug] " + issue)

        # Finally, make a CrashInfo object and parse stack traces for asan/crash/assertion bugs
        crashInfo = CrashInfo.CrashInfo.fromRawCrashData(
            out, err, pc, auxCrashData=auxCrashData)

        createCollector.printCrashInfo(crashInfo)
        # We only care about crashes and assertion failures on shells with no symbols
        # Note that looking out for the Assertion failure message is highly SpiderMonkey-specific
        if not isinstance(crashInfo, CrashInfo.NoCrashInfo) or \
                'Assertion failure: ' in str(crashInfo.rawStderr) or \
                'Segmentation fault' in str(crashInfo.rawStderr) or \
                'Bus error' in str(crashInfo.rawStderr):
            lev = max(lev, JS_NEW_ASSERT_OR_CRASH)

        match = options.collector.search(crashInfo)
        if match[0] is not None:
            createCollector.printMatchingSignature(match)
            lev = JS_FINE

        print("%s | %s" %
              (logPrefix, summaryString(issues, lev, runinfo.elapsedtime)))

        if lev != JS_FINE:
            fileManipulation.writeLinesToFile([
                'Number: ' + logPrefix + '\n',
                'Command: ' + sps.shellify(runthis) + '\n'
            ] + ['Status: ' + i + "\n" for i in issues],
                                              logPrefix + '-summary.txt')

        self.lev = lev
        self.out = out
        self.err = err
        self.issues = issues
        self.crashInfo = crashInfo
        self.match = match
        self.runinfo = runinfo
        self.return_code = runinfo.return_code
Exemple #7
0
    def __init__(self, options, runthis, logPrefix, in_compare_jit, env=None):  # pylint: disable=too-complex
        # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements

        # If Lithium uses this as an interestingness test, logPrefix is likely not a Path object, so make it one.
        logPrefix = Path(logPrefix)
        pathToBinary = runthis[0].expanduser().resolve()  # pylint: disable=invalid-name
        # This relies on the shell being a local one from compile_shell:
        # Ignore trailing ".exe" in Win, also abspath makes it work w/relative paths like "./js"
        # pylint: disable=invalid-name
        assert pathToBinary.with_suffix(".fuzzmanagerconf").is_file()
        pc = ProgramConfiguration.fromBinary(
            str(pathToBinary.parent / pathToBinary.stem))
        pc.addProgramArguments(runthis[1:-1])

        if options.valgrind:
            runthis = (inspect_shell.constructVgCmdList(
                errorCode=VALGRIND_ERROR_EXIT_CODE) + valgrindSuppressions() +
                       runthis)

        timed_run_kw = {}
        timed_run_kw["env"] = (env or os.environ)
        if not platform.system() == "Windows":
            timed_run_kw["preexec_fn"] = set_ulimit

        lithium_logPrefix = str(logPrefix).encode("utf-8")
        # Total hack to make Python 2/3 work with Lithium
        if sys.version_info.major == 3 and isinstance(lithium_logPrefix,
                                                      b"".__class__):
            # pylint: disable=redefined-variable-type
            lithium_logPrefix = lithium_logPrefix.decode("utf-8",
                                                         errors="replace")

        # logPrefix should be a string for timed_run in Lithium version 0.2.1 to work properly, apparently
        runinfo = timed_run.timed_run(
            [str(x) for x in runthis
             ],  # Convert all Paths/bytes to strings for Lithium
            options.timeout,
            lithium_logPrefix,
            **timed_run_kw)

        lev = JS_FINE
        issues = []
        auxCrashData = []  # pylint: disable=invalid-name

        # FuzzManager expects a list of strings rather than an iterable, so bite the
        # bullet and "readlines" everything into memory.
        out_log = (logPrefix.parent /
                   (logPrefix.stem + "-out")).with_suffix(".txt")
        with io.open(str(out_log), "r", encoding="utf-8",
                     errors="replace") as f:
            out = f.readlines()
        err_log = (logPrefix.parent /
                   (logPrefix.stem + "-err")).with_suffix(".txt")
        with io.open(str(err_log), "r", encoding="utf-8",
                     errors="replace") as f:
            err = f.readlines()

        if options.valgrind and runinfo.return_code == VALGRIND_ERROR_EXIT_CODE:
            issues.append("valgrind reported an error")
            lev = max(lev, JS_VG_AMISS)
            valgrindErrorPrefix = "==" + str(runinfo.pid) + "=="
            for line in err:
                if valgrindErrorPrefix and line.startswith(
                        valgrindErrorPrefix):
                    issues.append(line.rstrip())
        elif runinfo.sta == timed_run.CRASHED:
            if os_ops.grab_crash_log(runthis[0], runinfo.pid, logPrefix, True):
                crash_log = (logPrefix.parent /
                             (logPrefix.stem + "-crash")).with_suffix(".txt")
                with io.open(str(crash_log),
                             "r",
                             encoding="utf-8",
                             errors="replace") as f:
                    auxCrashData = [line.strip() for line in f.readlines()]
        elif file_manipulation.amiss(logPrefix):
            issues.append("malloc error")
            lev = max(lev, JS_NEW_ASSERT_OR_CRASH)
        elif runinfo.return_code == 0 and not in_compare_jit:
            # We might have(??) run jsfunfuzz directly, so check for special kinds of bugs
            for line in out:
                if line.startswith("Found a bug: ") and not ("NestTest" in line
                                                             and oomed(err)):
                    lev = JS_DECIDED_TO_EXIT
                    issues.append(line.rstrip())
            if options.shellIsDeterministic and not understoodJsfunfuzzExit(
                    out, err) and not oomed(err):
                issues.append("jsfunfuzz didn't finish")
                lev = JS_DID_NOT_FINISH

        # Copy non-crash issues to where FuzzManager's "AssertionHelper" can see it.
        if lev != JS_FINE:
            for issue in issues:
                err.append("[Non-crash bug] " + issue)

        activated = False  # Turn on when trying to report *reliable* testcases that do not have a coredump
        # On Linux, fall back to run testcase via gdb using --args if core file data is unavailable
        # Note that this second round of running uses a different fuzzSeed as the initial if default jsfunfuzz is run
        # We should separate this out, i.e. running jsfunfuzz within a debugger, only if core dumps cannot be generated
        if activated and platform.system() == "Linux" and which(
                "gdb") and not auxCrashData and not in_compare_jit:
            print(
                "Note: No core file found on Linux - falling back to run via gdb"
            )
            extracted_gdb_cmds = ["-ex", "run"]
            with io.open(str(
                    Path(__file__).parent.parent / "util" / "gdb_cmds.txt"),
                         "r",
                         encoding="utf-8",
                         errors="replace") as f:
                for line in f:
                    if line.rstrip() and not line.startswith(
                            "#") and not line.startswith("echo"):
                        extracted_gdb_cmds.append("-ex")
                        extracted_gdb_cmds.append("%s" % line.rstrip())
            no_main_log_gdb_log = subprocess.run(
                (["gdb", "-n", "-batch"] + extracted_gdb_cmds + ["--args"] +
                 [str(x) for x in runthis]),
                check=True,
                stderr=subprocess.PIPE,
                stdout=subprocess.PIPE)
            auxCrashData = no_main_log_gdb_log.stdout

        # Finally, make a CrashInfo object and parse stack traces for asan/crash/assertion bugs
        crashInfo = CrashInfo.CrashInfo.fromRawCrashData(
            out, err, pc, auxCrashData=auxCrashData)

        create_collector.printCrashInfo(crashInfo)
        # We only care about crashes and assertion failures on shells with no symbols
        # Note that looking out for the Assertion failure message is highly SpiderMonkey-specific
        if not isinstance(crashInfo, CrashInfo.NoCrashInfo) or \
                "Assertion failure: " in str(crashInfo.rawStderr) or \
                "Segmentation fault" in str(crashInfo.rawStderr) or \
                "Bus error" in str(crashInfo.rawStderr):
            lev = max(lev, JS_NEW_ASSERT_OR_CRASH)

        try:
            match = options.collector.search(crashInfo)
            if match[0] is not None:
                create_collector.printMatchingSignature(match)
                lev = JS_FINE
        except UnicodeDecodeError:  # Sometimes FM throws due to unicode issues
            print(
                "Note: FuzzManager is throwing a UnicodeDecodeError, signature matching skipped"
            )
            match = False

        print("%s | %s" %
              (logPrefix, summaryString(issues, lev, runinfo.elapsedtime)))

        if lev != JS_FINE:
            summary_log = (logPrefix.parent /
                           (logPrefix.stem + "-summary")).with_suffix(".txt")
            with io.open(str(summary_log),
                         "w",
                         encoding="utf-8",
                         errors="replace") as f:
                f.writelines([
                    "Number: " + str(logPrefix) + "\n", "Command: " +
                    " ".join(quote(str(x)) for x in runthis) + "\n"
                ] + ["Status: " + i + "\n" for i in issues])

        self.lev = lev
        self.out = out
        self.err = err
        self.issues = issues
        self.crashInfo = crashInfo  # pylint: disable=invalid-name
        self.match = match
        self.runinfo = runinfo
        self.return_code = runinfo.return_code