def createReproFile(fuzzerJS, extraPrefs, lines, logPrefix): contentTypes = linesStartingWith(lines, "FRCX Content type: ") contentType = afterColon( contentTypes[0]) if len(contentTypes) > 0 else "text/html" extDict = { 'text/html': 'html', 'application/xhtml+xml': 'xhtml', 'image/svg+xml': 'svg', 'application/vnd.mozilla.xul+xml': 'xul', # 'text/xml' is tricky. We'd want to know the xmlns of the root, and steal its contents but use .xml. # But treating it as xhtml is better than doing nothing, for now. 'text/xml': 'xhtml' } if contentType in extDict: extension = extDict[contentType] else: print "loopdomfuzz is not sure what to do with content type " + repr( contentType) + " :(" extension = "xhtml" [wbefore, wafter] = fuzzDice(os.path.join(emptiesDir, "a." + extension)) possibleDoctype = [] if contentType == "text/html": docTypes = linesStartingWith(lines, "FRCX Doctype: ") if len(docTypes) > 0: possibleDoctype = [afterColon(docTypes[0]) + "\n"] [jbefore, jafter] = fuzzSplice(fuzzerJS) fuzzlines = [ line[12:] for line in linesStartingWith(lines, " /*FRCA1*/ ") ] if len(fuzzlines) < 3: fuzzlines = [ "// Startup crash?\n", "var fuzzSettings = [42,0,42,42,3000,0];\n", "var fuzzCommands = [];\n", "// DDBEGIN\n" ] quittage = [ extraPrefs, "// DDEND\n", 'fuzzCommands.push({ note: "done", rest: true, timeout: 3000});\n', 'fuzzCommands.push({ note: "quit", fun: function() { fuzzPriv.quitApplication(); } });\n', "\n", "function user_pref() { /* Allow randomPrefs.py to parse user_pref lines from this file */ }\n", ] linesToWrite = possibleDoctype + wbefore + jbefore + fuzzlines + quittage + jafter + wafter oFN = logPrefix + "-splice-orig." + extension rFN = logPrefix + "-splice-reduced." + extension writeLinesToFile(linesToWrite, oFN) writeLinesToFile(linesToWrite, rFN) subprocess.call(["gzip", oFN]) return rFN
def createReproFile(fuzzerJS, extraPrefs, lines, logPrefix): contentTypes = linesStartingWith(lines, "FRCX Content type: ") contentType = afterColon(contentTypes[0]) if len(contentTypes) > 0 else "text/html" extDict = { "text/html": "html", "application/xhtml+xml": "xhtml", "image/svg+xml": "svg", "application/vnd.mozilla.xul+xml": "xul", # 'text/xml' is tricky. We'd want to know the xmlns of the root, and steal its contents but use .xml. # But treating it as xhtml is better than doing nothing, for now. "text/xml": "xhtml", } if contentType in extDict: extension = extDict[contentType] else: print "loopdomfuzz is not sure what to do with content type " + repr(contentType) + " :(" extension = "xhtml" [wbefore, wafter] = fuzzDice(os.path.join(emptiesDir, "a." + extension)) possibleDoctype = [] if contentType == "text/html": docTypes = linesStartingWith(lines, "FRCX Doctype: ") if len(docTypes) > 0: possibleDoctype = [afterColon(docTypes[0]) + "\n"] [jbefore, jafter] = fuzzSplice(fuzzerJS) fuzzlines = [line[12:] for line in linesStartingWith(lines, " /*FRCA1*/ ")] if len(fuzzlines) < 3: fuzzlines = [ "// Startup crash?\n", "var fuzzSettings = [42,0,42,42,3000,0];\n", "var fuzzCommands = [];\n", "// DDBEGIN\n", ] quittage = [ extraPrefs, "// DDEND\n", 'fuzzCommands.push({ note: "done", rest: true, timeout: 3000});\n', 'fuzzCommands.push({ note: "quit", fun: function() { fuzzPriv.quitApplication(); } });\n', "\n", "function user_pref() { /* Allow randomPrefs.py to parse user_pref lines from this file */ }\n", ] linesToWrite = possibleDoctype + wbefore + jbefore + fuzzlines + quittage + jafter + wafter oFN = logPrefix + "-splice-orig." + extension rFN = logPrefix + "-splice-reduced." + extension writeLinesToFile(linesToWrite, oFN) writeLinesToFile(linesToWrite, rFN) subprocess.call(["gzip", oFN]) return rFN
def many_timed_runs(targetTime, wtmpDir, args, collector): options = parseOpts(args) engineFlags = options.engineFlags # engineFlags is overwritten later if --random-flags is set. startTime = time.time() if os.path.isdir(sps.normExpUserPath(options.repo)): regressionTestListFile = sps.normExpUserPath(os.path.join(wtmpDir, "regression-tests.list")) with open(regressionTestListFile, "wb") as f: for fn in inTreeRegressionTests(options.repo): f.write(fn + "\n") regressionTestPrologue = makeRegressionTestPrologue(options.repo, regressionTestListFile) else: regressionTestPrologue = "" fuzzjs = sps.normExpUserPath(os.path.join(wtmpDir, "jsfunfuzz.js")) linkFuzzer(fuzzjs, options.repo, regressionTestPrologue) iteration = 0 while True: if targetTime and time.time() > startTime + targetTime: print "Out of time!" os.remove(fuzzjs) if len(os.listdir(wtmpDir)) == 0: os.rmdir(wtmpDir) break # Construct command needed to loop jsfunfuzz fuzzing. jsInterestingArgs = [] jsInterestingArgs.append('--timeout=' + str(options.timeout)) if options.valgrind: jsInterestingArgs.append('--valgrind') jsInterestingArgs.append(options.knownPath) jsInterestingArgs.append(options.jsEngine) if options.randomFlags: engineFlags = shellFlags.randomFlagSet(options.jsEngine) jsInterestingArgs.extend(engineFlags) jsInterestingArgs.extend(['-e', 'maxRunTime=' + str(options.timeout*(1000/2))]) jsInterestingArgs.extend(['-f', fuzzjs]) jsInterestingOptions = jsInteresting.parseOptions(jsInterestingArgs) iteration += 1 logPrefix = sps.normExpUserPath(os.path.join(wtmpDir, "w" + str(iteration))) res = jsInteresting.ShellResult(jsInterestingOptions, jsInterestingOptions.jsengineWithArgs, logPrefix, False) if res.lev != jsInteresting.JS_FINE: showtail(logPrefix + "-out.txt") showtail(logPrefix + "-err.txt") # splice jsfunfuzz.js with `grep FRC wN-out` filenameToReduce = logPrefix + "-reduced.js" [before, after] = fileManipulation.fuzzSplice(fuzzjs) with open(logPrefix + '-out.txt', 'rb') as f: newfileLines = before + [l.replace('/*FRC*/', '') for l in fileManipulation.linesStartingWith(f, "/*FRC*/")] + after fileManipulation.writeLinesToFile(newfileLines, logPrefix + "-orig.js") fileManipulation.writeLinesToFile(newfileLines, filenameToReduce) # Run Lithium and autobisect (make a reduced testcase and find a regression window) itest = [interestingpy] if options.valgrind: itest.append("--valgrind") itest.append("--minlevel=" + str(res.lev)) itest.append("--timeout=" + str(options.timeout)) itest.append(options.knownPath) (lithResult, lithDetails, autoBisectLog) = pinpoint.pinpoint(itest, logPrefix, options.jsEngine, engineFlags, filenameToReduce, options.repo, options.buildOptionsStr, targetTime, res.lev) # Upload with final output if lithResult == lithOps.LITH_FINISHED: fargs = jsInterestingOptions.jsengineWithArgs[:-1] + [filenameToReduce] retestResult = jsInteresting.ShellResult(jsInterestingOptions, fargs, logPrefix + "-final", False) if retestResult.lev > jsInteresting.JS_FINE: res = retestResult quality = 0 else: quality = 6 else: quality = 10 # ddsize = lithOps.ddsize(filenameToReduce) print "Submitting " + filenameToReduce + " (quality=" + str(quality) + ") at " + sps.dateStr() metadata = {} if autoBisectLog: metadata = {"autoBisectLog": ''.join(autoBisectLog)} collector.submit(res.crashInfo, filenameToReduce, quality, metaData=metadata) print "Submitted " + filenameToReduce else: flagsAreDeterministic = "--dump-bytecode" not in engineFlags and '-D' not in engineFlags if options.useCompareJIT and res.lev == jsInteresting.JS_FINE and \ jsInterestingOptions.shellIsDeterministic and flagsAreDeterministic: linesToCompare = jitCompareLines(logPrefix + '-out.txt', "/*FCM*/") jitcomparefilename = logPrefix + "-cj-in.js" fileManipulation.writeLinesToFile(linesToCompare, jitcomparefilename) anyBug = compareJIT.compareJIT(options.jsEngine, engineFlags, jitcomparefilename, logPrefix + "-cj", options.repo, options.buildOptionsStr, targetTime, jsInterestingOptions) if not anyBug: os.remove(jitcomparefilename) jsInteresting.deleteLogs(logPrefix)
def strategicReduction(logPrefix, infilename, lithArgs, targetTime, lev): '''Reduce jsfunfuzz output files using Lithium by using various strategies.''' reductionCount = [ 0 ] # This is an array because Python does not like assigning to upvars. backupFilename = infilename + '-backup' def lithReduceCmd(strategy): '''Lithium reduction commands accepting various strategies.''' reductionCount[0] += 1 fullLithArgs = [x for x in (strategy + lithArgs) if x] # Remove empty elements print sps.shellify([lithiumpy] + fullLithArgs) desc = '-chars' if strategy == '--char' else '-lines' (lithResult, lithDetails) = runLithium( fullLithArgs, logPrefix + "-" + str(reductionCount[0]) + desc, targetTime) if lithResult == LITH_FINISHED: shutil.copy2(infilename, backupFilename) return lithResult, lithDetails print '\nRunning the first line reduction...\n' # Step 1: Run the first instance of line reduction. lithResult, lithDetails = lithReduceCmd([]) if lithDetails is not None: # lithDetails can be None if testcase no longer becomes interesting origNumOfLines = int(lithDetails.split()[0]) hasTryItOut = False hasTryItOutRegex = re.compile('count=[0-9]+; tryItOut\("') with open(infilename, 'rb') as f: for line in linesWith(f, '; tryItOut("'): # Checks if testcase came from jsfunfuzz or compareJIT. hasTryItOut = hasTryItOutRegex.match(line) if hasTryItOut: # Stop searching after finding the first tryItOut line. break # Step 2: Run 1 instance of 1-line reduction after moving tryItOut and count=X around. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: tryItOutAndCountRegex = re.compile('"\);\ncount=([0-9]+); tryItOut\("', re.MULTILINE) with open(infilename, 'rb') as f: infileContents = f.read() infileContents = re.sub(tryItOutAndCountRegex, ';\\\n"); count=\\1; tryItOut("\\\n', infileContents) with open(infilename, 'wb') as f: f.write(infileContents) print '\nRunning 1 instance of 1-line reduction after moving tryItOut and count=X...\n' # --chunksize=1: Reduce only individual lines, for only 1 round. lithResult, lithDetails = lithReduceCmd(['--chunksize=1']) # Step 3: Run 1 instance of 2-line reduction after moving count=X to its own line and add a # 1-line offset. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: intendedLines = [] with open(infilename, 'rb') as f: for line in f.readlines( ): # The testcase is likely to already be partially reduced. if 'dumpln(cookie' not in line: # jsfunfuzz-specific line ignore # This should be simpler than re.compile. intendedLines.append( line.replace('; count=', ';\ncount=').replace( '; tryItOut("', ';\ntryItOut("') # The 1-line offset is added here. .replace('SPLICE DDBEGIN', 'SPLICE DDBEGIN\n')) writeLinesToFile(intendedLines, infilename) print '\nRunning 1 instance of 2-line reduction after moving count=X to its own line...\n' lithResult, lithDetails = lithReduceCmd(['--chunksize=2']) # Step 4: Run 1 instance of 2-line reduction again, e.g. to remove pairs of STRICT_MODE lines. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: print '\nRunning 1 instance of 2-line reduction again...\n' lithResult, lithDetails = lithReduceCmd(['--chunksize=2']) isLevOverallMismatchAsmJsAvailable = (lev == JS_OVERALL_MISMATCH) and \ fileContainsStr(infilename, 'isAsmJSCompilationAvailable') # Step 5 (not always run): Run character reduction within interesting lines. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and targetTime is None and \ lev >= JS_OVERALL_MISMATCH and not isLevOverallMismatchAsmJsAvailable: print '\nRunning character reduction...\n' lithResult, lithDetails = lithReduceCmd(['--char']) # Step 6: Run line reduction after activating SECOND DDBEGIN with a 1-line offset. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: infileContents = [] with open(infilename, 'rb') as f: for line in f.readlines(): if 'NIGEBDD' in line: infileContents.append(line.replace('NIGEBDD', 'DDBEGIN')) infileContents.append( '\n') # The 1-line offset is added here. continue infileContents.append(line) with open(infilename, 'wb') as f: f.writelines(infileContents) print '\nRunning line reduction with a 1-line offset...\n' lithResult, lithDetails = lithReduceCmd([]) # Step 7: Run line reduction for a final time. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: print '\nRunning the final line reduction...\n' lithResult, lithDetails = lithReduceCmd([]) # Restore from backup if testcase can no longer be reproduced halfway through reduction. if lithResult != LITH_FINISHED and lithResult != LITH_PLEASE_CONTINUE: # Probably can move instead of copy the backup, once this has stabilised. if os.path.isfile(backupFilename): shutil.copy2(backupFilename, infilename) else: print 'DEBUG! backupFilename is supposed to be: ' + backupFilename return lithResult, lithDetails
def strategicReduction(logPrefix, infilename, lithArgs, targetTime, lev): """Reduce jsfunfuzz output files using Lithium by using various strategies.""" reductionCount = [0] # This is an array because Python does not like assigning to upvars. backupFilename = infilename + '-backup' def lithReduceCmd(strategy): """Lithium reduction commands accepting various strategies.""" reductionCount[0] += 1 fullLithArgs = [x for x in (strategy + lithArgs) if x] # Remove empty elements print sps.shellify([lithiumpy] + fullLithArgs) desc = '-chars' if strategy == '--char' else '-lines' (lithResult, lithDetails) = runLithium(fullLithArgs, logPrefix + "-" + str(reductionCount[0]) + desc, targetTime) if lithResult == LITH_FINISHED: shutil.copy2(infilename, backupFilename) return lithResult, lithDetails print '\nRunning the first line reduction...\n' # Step 1: Run the first instance of line reduction. lithResult, lithDetails = lithReduceCmd([]) if lithDetails is not None: # lithDetails can be None if testcase no longer becomes interesting origNumOfLines = int(lithDetails.split()[0]) hasTryItOut = False hasTryItOutRegex = re.compile(r'count=[0-9]+; tryItOut\("') with open(infilename, 'rb') as f: for line in fileManipulation.linesWith(f, '; tryItOut("'): # Checks if testcase came from jsfunfuzz or compareJIT. # Do not use .match here, it only matches from the start of the line: # https://docs.python.org/2/library/re.html#search-vs-match hasTryItOut = hasTryItOutRegex.search(line) if hasTryItOut: # Stop searching after finding the first tryItOut line. break # Step 2: Run 1 instance of 1-line reduction after moving tryItOut and count=X around. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: tryItOutAndCountRegex = re.compile(r'"\);\ncount=([0-9]+); tryItOut\("', re.MULTILINE) with open(infilename, 'rb') as f: infileContents = f.read() infileContents = re.sub(tryItOutAndCountRegex, ';\\\n"); count=\\1; tryItOut("\\\n', infileContents) with open(infilename, 'wb') as f: f.write(infileContents) print '\nRunning 1 instance of 1-line reduction after moving tryItOut and count=X...\n' # --chunksize=1: Reduce only individual lines, for only 1 round. lithResult, lithDetails = lithReduceCmd(['--chunksize=1']) # Step 3: Run 1 instance of 2-line reduction after moving count=X to its own line and add a # 1-line offset. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: intendedLines = [] with open(infilename, 'rb') as f: for line in f: # The testcase is likely to already be partially reduced. if 'dumpln(cookie' not in line: # jsfunfuzz-specific line ignore # This should be simpler than re.compile. intendedLines.append(line.replace('; count=', ';\ncount=') .replace('; tryItOut("', ';\ntryItOut("') # The 1-line offset is added here. .replace('SPLICE DDBEGIN', 'SPLICE DDBEGIN\n')) fileManipulation.writeLinesToFile(intendedLines, infilename) print '\nRunning 1 instance of 2-line reduction after moving count=X to its own line...\n' lithResult, lithDetails = lithReduceCmd(['--chunksize=2']) # Step 4: Run 1 instance of 2-line reduction again, e.g. to remove pairs of STRICT_MODE lines. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: print '\nRunning 1 instance of 2-line reduction again...\n' lithResult, lithDetails = lithReduceCmd(['--chunksize=2']) isLevOverallMismatchAsmJsAvailable = (lev == JS_OVERALL_MISMATCH) and \ fileContainsStr(infilename, 'isAsmJSCompilationAvailable') # Step 5 (not always run): Run character reduction within interesting lines. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and targetTime is None and \ lev >= JS_OVERALL_MISMATCH and not isLevOverallMismatchAsmJsAvailable: print '\nRunning character reduction...\n' lithResult, lithDetails = lithReduceCmd(['--char']) # Step 6: Run line reduction after activating SECOND DDBEGIN with a 1-line offset. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: infileContents = [] with open(infilename, 'rb') as f: for line in f: if 'NIGEBDD' in line: infileContents.append(line.replace('NIGEBDD', 'DDBEGIN')) infileContents.append('\n') # The 1-line offset is added here. continue infileContents.append(line) with open(infilename, 'wb') as f: f.writelines(infileContents) print '\nRunning line reduction with a 1-line offset...\n' lithResult, lithDetails = lithReduceCmd([]) # Step 7: Run line reduction for a final time. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: print '\nRunning the final line reduction...\n' lithResult, lithDetails = lithReduceCmd([]) # Restore from backup if testcase can no longer be reproduced halfway through reduction. if lithResult != LITH_FINISHED and lithResult != LITH_PLEASE_CONTINUE: # Probably can move instead of copy the backup, once this has stabilised. if os.path.isfile(backupFilename): shutil.copy2(backupFilename, infilename) else: print 'DEBUG! backupFilename is supposed to be: ' + backupFilename return lithResult, lithDetails
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
def many_timed_runs(targetTime, wtmpDir, args): options = parseOpts(args) engineFlags = options.engineFlags # engineFlags is overwritten later if --random-flags is set. startTime = time.time() if os.path.isdir(sps.normExpUserPath(options.repo)): regressionTestListFile = sps.normExpUserPath( os.path.join(wtmpDir, "regression-tests.list")) with open(regressionTestListFile, "wb") as f: for fn in inTreeRegressionTests(options.repo): f.write(fn + "\n") regressionTestPrologue = makeRegressionTestPrologue( options.repo, regressionTestListFile) else: regressionTestPrologue = "" fuzzjs = sps.normExpUserPath(os.path.join(wtmpDir, "jsfunfuzz.js")) linkFuzzer(fuzzjs, options.repo, regressionTestPrologue) iteration = 0 while True: if targetTime and time.time() > startTime + targetTime: print "Out of time!" os.remove(fuzzjs) if len(os.listdir(wtmpDir)) == 0: os.rmdir(wtmpDir) return (lithOps.HAPPY, None) # Construct command needed to loop jsfunfuzz fuzzing. jsInterestingArgs = [] jsInterestingArgs.append('--timeout=' + str(options.timeout)) if options.valgrind: jsInterestingArgs.append('--valgrind') jsInterestingArgs.append(options.knownPath) jsInterestingArgs.append(options.jsEngine) if options.randomFlags: engineFlags = shellFlags.randomFlagSet(options.jsEngine) jsInterestingArgs.extend(engineFlags) jsInterestingArgs.extend( ['-e', 'maxRunTime=' + str(options.timeout * (1000 / 2))]) jsInterestingArgs.extend(['-f', fuzzjs]) jsunhappyOptions = jsInteresting.parseOptions(jsInterestingArgs) iteration += 1 logPrefix = sps.normExpUserPath( os.path.join(wtmpDir, "w" + str(iteration))) level = jsInteresting.jsfunfuzzLevel(jsunhappyOptions, logPrefix) if level != jsInteresting.JS_FINE: showtail(logPrefix + "-out.txt") showtail(logPrefix + "-err.txt") # splice jsfunfuzz.js with `grep FRC wN-out` filenameToReduce = logPrefix + "-reduced.js" [before, after] = fileManipulation.fuzzSplice(fuzzjs) with open(logPrefix + '-out.txt', 'rb') as f: newfileLines = before + [ l.replace('/*FRC*/', '') for l in fileManipulation.linesStartingWith(f, "/*FRC*/") ] + after fileManipulation.writeLinesToFile(newfileLines, logPrefix + "-orig.js") fileManipulation.writeLinesToFile(newfileLines, filenameToReduce) # Run Lithium and autobisect (make a reduced testcase and find a regression window) itest = [interestingpy] if options.valgrind: itest.append("--valgrind") itest.append("--minlevel=" + str(level)) itest.append("--timeout=" + str(options.timeout)) itest.append(options.knownPath) (lithResult, lithDetails) = pinpoint.pinpoint( itest, logPrefix, options.jsEngine, engineFlags, filenameToReduce, options.repo, options.buildOptionsStr, targetTime, level) if targetTime: return (lithResult, lithDetails) else: shellIsDeterministic = inspectShell.queryBuildConfiguration( options.jsEngine, 'more-deterministic') flagsAreDeterministic = "--dump-bytecode" not in engineFlags and '-D' not in engineFlags if options.useCompareJIT and level == jsInteresting.JS_FINE and \ shellIsDeterministic and flagsAreDeterministic: linesToCompare = jitCompareLines(logPrefix + '-out.txt', "/*FCM*/") jitcomparefilename = logPrefix + "-cj-in.js" fileManipulation.writeLinesToFile(linesToCompare, jitcomparefilename) (lithResult, lithDetails) = compareJIT.compareJIT( options.jsEngine, engineFlags, jitcomparefilename, logPrefix + "-cj", options.knownPath, options.repo, options.buildOptionsStr, options.timeout, targetTime) if lithResult == lithOps.HAPPY: os.remove(jitcomparefilename) if targetTime and lithResult != lithOps.HAPPY: jsInteresting.deleteLogs(logPrefix) return (lithResult, lithDetails) jsInteresting.deleteLogs(logPrefix)
def __init__(self, options, runthis, logPrefix, inCompareJIT): pathToBinary = runthis[0] # This relies on the shell being a local one from compileShell.py: pc = ProgramConfiguration.fromBinary(pathToBinary.split('.')[0]) # Ignore trailing ".exe" in Win 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 = timedRun.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.rc == 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 == timedRun.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.rc == 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) if not isinstance(crashInfo, CrashInfo.NoCrashInfo): 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 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.rc = runinfo.rc
def jsfunfuzzLevel(options, logPrefix, quiet=False): (lev, issues, runinfo) = baseLevel( options.jsengineWithArgs, options.timeout, options.knownPath, logPrefix, valgrind=options.valgrind ) if lev == JS_FINE: # Check for unexplained exits and for jsfunfuzz saying "Found a bug". understoodExit = False # Read in binary mode, because otherwise Python on Windows will # throw a fit when it encounters certain unicode. Note that this # makes line endings platform-specific. if "-dm-" in options.jsengineWithArgs[0]: # Since this is an --enable-more-deterministic build, we should get messages on stderr # if the shell quit() or terminate() functions are called. # (We use a sketchy filename-matching check because it's faster than inspecting the binary.) with open(logPrefix + "-err.txt", "rb") as f: for line in f: if "terminate called" in line or "quit called" in line: understoodExit = True if "can't allocate region" in line: understoodExit = True else: understoodExit = True with open(logPrefix + "-out.txt", "rb") as f: for line in f: if line.startswith("It's looking good!") or line.startswith( "jsfunfuzz broke its own scripting environment: " ): understoodExit = True if line.startswith("Found a bug: "): understoodExit = True if not ("NestTest" in line and oomed(logPrefix)): lev = JS_DECIDED_TO_EXIT issues.append(line.rstrip()) # FIXME: if not quiet: # FIXME: output everything between this line and "jsfunfuzz stopping due to finding a bug." if not understoodExit: issues.append("jsfunfuzz didn't finish") lev = JS_DID_NOT_FINISH # FIXME: if not quiet: # FIXME: output the last tryItOut line if lev <= JS_ABNORMAL_EXIT: # JS_ABNORMAL_EXIT and below (inclusive) will be ignored. sps.vdump("jsfunfuzzLevel is ignoring a baseLevel of " + str(lev)) lev = JS_FINE issues = [] if lev != JS_FINE: # FIXME: compareJIT failures do not generate this -summary file. statusIssueList = [] for i in issues: statusIssueList.append("Status: " + i) assert len(statusIssueList) != 0 fileManipulation.writeLinesToFile( ["Number: " + logPrefix + "\n", "Command: " + sps.shellify(options.jsengineWithArgs) + "\n"] + [i + "\n" for i in statusIssueList], logPrefix + "-summary.txt", ) if not quiet: print logPrefix + " | " + summaryString(issues, lev, runinfo.elapsedtime) return lev
def jsfunfuzzLevel(options, logPrefix, quiet=False): (lev, issues, runinfo) = baseLevel(options.jsengineWithArgs, options.timeout, options.knownPath, logPrefix, valgrind=options.valgrind) if lev == JS_FINE: # Check for unexplained exits and for jsfunfuzz saying "Found a bug". understoodExit = False # Read in binary mode, because otherwise Python on Windows will # throw a fit when it encounters certain unicode. Note that this # makes line endings platform-specific. if '-dm-' in options.jsengineWithArgs[0]: # Since this is an --enable-more-deterministic build, we should get messages on stderr # if the shell quit() or terminate() functions are called. # (We use a sketchy filename-matching check because it's faster than inspecting the binary.) with open(logPrefix + "-err.txt", "rb") as f: for line in f: if "terminate called" in line or "quit called" in line: understoodExit = True if "can't allocate region" in line: understoodExit = True else: understoodExit = True with open(logPrefix + "-out.txt", "rb") as f: for line in f: if line.startswith("It's looking good!") or line.startswith( "jsfunfuzz broke its own scripting environment: "): understoodExit = True if line.startswith("Found a bug: "): understoodExit = True if not ("NestTest" in line and oomed(logPrefix)): lev = JS_DECIDED_TO_EXIT issues.append(line.rstrip()) # FIXME: if not quiet: # FIXME: output everything between this line and "jsfunfuzz stopping due to finding a bug." if not understoodExit: issues.append("jsfunfuzz didn't finish") lev = JS_DID_NOT_FINISH # FIXME: if not quiet: # FIXME: output the last tryItOut line if lev <= JS_ABNORMAL_EXIT: # JS_ABNORMAL_EXIT and below (inclusive) will be ignored. sps.vdump("jsfunfuzzLevel is ignoring a baseLevel of " + str(lev)) lev = JS_FINE issues = [] if lev != JS_FINE: # FIXME: compareJIT failures do not generate this -summary file. statusIssueList = [] for i in issues: statusIssueList.append('Status: ' + i) assert len(statusIssueList) != 0 fileManipulation.writeLinesToFile([ 'Number: ' + logPrefix + '\n', 'Command: ' + sps.shellify(options.jsengineWithArgs) + '\n' ] + [i + '\n' for i in statusIssueList], logPrefix + '-summary.txt') if not quiet: print logPrefix + " | " + summaryString(issues, lev, runinfo.elapsedtime) return lev
def many_timed_runs(targetTime, wtmpDir, args): options = parseOpts(args) engineFlags = options.engineFlags # engineFlags is overwritten later if --random-flags is set. startTime = time.time() if os.path.isdir(sps.normExpUserPath(options.repo)): regressionTestListFile = sps.normExpUserPath(os.path.join(wtmpDir, "regression-tests.list")) with open(regressionTestListFile, "wb") as f: for fn in inTreeRegressionTests(options.repo): f.write(fn + "\n") regressionTestPrologue = makeRegressionTestPrologue(options.repo, regressionTestListFile) else: regressionTestPrologue = "" fuzzjs = sps.normExpUserPath(os.path.join(wtmpDir, "jsfunfuzz.js")) linkFuzzer(fuzzjs, options.repo, regressionTestPrologue) iteration = 0 while True: if targetTime and time.time() > startTime + targetTime: print "Out of time!" os.remove(fuzzjs) if len(os.listdir(wtmpDir)) == 0: os.rmdir(wtmpDir) return (lithOps.HAPPY, None) # Construct command needed to loop jsfunfuzz fuzzing. jsInterestingArgs = [] jsInterestingArgs.append('--timeout=' + str(options.timeout)) if options.valgrind: jsInterestingArgs.append('--valgrind') jsInterestingArgs.append(options.knownPath) jsInterestingArgs.append(options.jsEngine) if options.randomFlags: engineFlags = shellFlags.randomFlagSet(options.jsEngine) jsInterestingArgs.extend(engineFlags) jsInterestingArgs.extend(['-e', 'maxRunTime=' + str(options.timeout*(1000/2))]) jsInterestingArgs.extend(['-f', fuzzjs]) jsunhappyOptions = jsInteresting.parseOptions(jsInterestingArgs) iteration += 1 logPrefix = sps.normExpUserPath(os.path.join(wtmpDir, "w" + str(iteration))) level = jsInteresting.jsfunfuzzLevel(jsunhappyOptions, logPrefix) if level != jsInteresting.JS_FINE: showtail(logPrefix + "-out.txt") showtail(logPrefix + "-err.txt") # splice jsfunfuzz.js with `grep FRC wN-out` filenameToReduce = logPrefix + "-reduced.js" [before, after] = fileManipulation.fuzzSplice(fuzzjs) with open(logPrefix + '-out.txt', 'rb') as f: newfileLines = before + [l.replace('/*FRC*/', '') for l in fileManipulation.linesStartingWith(f, "/*FRC*/")] + after fileManipulation.writeLinesToFile(newfileLines, logPrefix + "-orig.js") fileManipulation.writeLinesToFile(newfileLines, filenameToReduce) # Run Lithium and autobisect (make a reduced testcase and find a regression window) itest = [interestingpy] if options.valgrind: itest.append("--valgrind") itest.append("--minlevel=" + str(level)) itest.append("--timeout=" + str(options.timeout)) itest.append(options.knownPath) (lithResult, lithDetails) = pinpoint.pinpoint(itest, logPrefix, options.jsEngine, engineFlags, filenameToReduce, options.repo, options.buildOptionsStr, targetTime, level) if targetTime: return (lithResult, lithDetails) else: shellIsDeterministic = inspectShell.queryBuildConfiguration(options.jsEngine, 'more-deterministic') flagsAreDeterministic = "--dump-bytecode" not in engineFlags and '-D' not in engineFlags if options.useCompareJIT and level == jsInteresting.JS_FINE and \ shellIsDeterministic and flagsAreDeterministic: linesToCompare = jitCompareLines(logPrefix + '-out.txt', "/*FCM*/") jitcomparefilename = logPrefix + "-cj-in.js" fileManipulation.writeLinesToFile(linesToCompare, jitcomparefilename) (lithResult, lithDetails) = compareJIT.compareJIT(options.jsEngine, engineFlags, jitcomparefilename, logPrefix + "-cj", options.knownPath, options.repo, options.buildOptionsStr, options.timeout, targetTime) if lithResult == lithOps.HAPPY: os.remove(jitcomparefilename) if targetTime and lithResult != lithOps.HAPPY: jsInteresting.deleteLogs(logPrefix) return (lithResult, lithDetails) jsInteresting.deleteLogs(logPrefix)