def strategicReduction(logPrefix, infilename, lithArgs, targetTime, lev): # pylint: disable=invalid-name # pylint: disable=missing-param-doc,missing-return-doc,missing-return-type-doc,missing-type-doc,too-complex # pylint: disable=too-many-branches,too-many-locals,too-many-statements """Reduce jsfunfuzz output files using Lithium by using various strategies.""" # This is an array because Python does not like assigning to upvars. reductionCount = [0] # pylint: disable=invalid-name backupFilename = infilename + '-backup' # pylint: disable=invalid-name def lithReduceCmd(strategy): # pylint: disable=invalid-name,missing-param-doc,missing-return-doc # pylint: disable=missing-return-type-doc,missing-type-doc """Lithium reduction commands accepting various strategies.""" reductionCount[0] += 1 # Remove empty elements fullLithArgs = [x for x in (strategy + lithArgs) if x] # pylint: disable=invalid-name print( sps.shellify([sys.executable, "-u", "-m", "lithium"] + fullLithArgs)) desc = '-chars' if strategy == '--char' else '-lines' (lithResult, lithDetails) = runLithium( # pylint: disable=invalid-name fullLithArgs, "%s-%s%s" % (logPrefix, reductionCount[0], desc), targetTime) if lithResult == LITH_FINISHED: shutil.copy2(infilename, backupFilename) return lithResult, lithDetails print() print("Running the first line reduction...") print() # Step 1: Run the first instance of line reduction. lithResult, lithDetails = lithReduceCmd([]) # pylint: disable=invalid-name if lithDetails is not None: # lithDetails can be None if testcase no longer becomes interesting origNumOfLines = int(lithDetails.split()[0]) # pylint: disable=invalid-name hasTryItOut = False # pylint: disable=invalid-name hasTryItOutRegex = re.compile(r'count=[0-9]+; tryItOut\("') # pylint: disable=invalid-name with open(infilename, 'r') as f: for line in file_manipulation.linesWith(f, '; tryItOut("'): # Checks if testcase came from jsfunfuzz or compare_jit. # 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) # pylint: disable=invalid-name 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\("', # pylint: disable=invalid-name re.MULTILINE) with open(infilename, 'r') as f: infileContents = f.read() # pylint: disable=invalid-name infileContents = re.sub( tryItOutAndCountRegex, # pylint: disable=invalid-name ';\\\n"); count=\\1; tryItOut("\\\n', infileContents) with open(infilename, 'w') as f: f.write(infileContents) print() print( "Running 1 instance of 1-line reduction after moving tryItOut and count=X..." ) print() # --chunksize=1: Reduce only individual lines, for only 1 round. lithResult, lithDetails = lithReduceCmd(['--chunksize=1']) # pylint: disable=invalid-name # 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 = [] # pylint: disable=invalid-name with open(infilename, 'r') 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')) with open(infilename, "w") as f: f.writelines(intendedLines) print() print( "Running 1 instance of 2-line reduction after moving count=X to its own line..." ) print() lithResult, lithDetails = lithReduceCmd(['--chunksize=2']) # pylint: disable=invalid-name # 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() print("Running 1 instance of 2-line reduction again...") print() lithResult, lithDetails = lithReduceCmd(['--chunksize=2']) # pylint: disable=invalid-name isLevOverallMismatchAsmJsAvailable = ( lev == JS_OVERALL_MISMATCH and # pylint: disable=invalid-name file_contains_str(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() print("Running character reduction...") print() lithResult, lithDetails = lithReduceCmd(['--char']) # pylint: disable=invalid-name # 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 = [] # pylint: disable=invalid-name with open(infilename, 'r') 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, 'w') as f: f.writelines(infileContents) print() print("Running line reduction with a 1-line offset...") print() lithResult, lithDetails = lithReduceCmd([]) # pylint: disable=invalid-name # Step 7: Run line reduction for a final time. if lithResult == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: print() print("Running the final line reduction...") print() lithResult, lithDetails = lithReduceCmd([]) # pylint: disable=invalid-name # 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: %s" % backupFilename) return lithResult, lithDetails
def reduction_strat(logPrefix, infilename, lithArgs, targetTime, lev): # pylint: disable=invalid-name # pylint: disable=missing-param-doc,missing-return-doc,missing-return-type-doc,missing-type-doc,too-complex # pylint: disable=too-many-branches,too-many-locals,too-many-statements """Reduce jsfunfuzz output files using Lithium by using various strategies.""" # This is an array because Python does not like assigning to upvars. reductionCount = [0] # pylint: disable=invalid-name backup_file = (logPrefix.parent / (logPrefix.stem + "-backup")) def lith_reduce(strategy): """Lithium reduction commands accepting various strategies. Args: strategy (str): Intended strategy to use Returns: (tuple): The finished Lithium run result and details """ reductionCount[0] += 1 # Remove empty elements full_lith_args = [x for x in (strategy + lithArgs) if x] print(" ".join(quote(str(x)) for x in [sys.executable, "-u", "-m", "lithium"] + full_lith_args)) desc = "-chars" if strategy == "--char" else "-lines" (lith_result, lith_details) = run_lithium( full_lith_args, (logPrefix.parent / ("%s-%s%s" % (logPrefix.stem, reductionCount[0], desc))), targetTime) if lith_result == LITH_FINISHED: shutil.copy2(str(infilename), str(backup_file)) return lith_result, lith_details print() print("Running the first line reduction...") print() # Step 1: Run the first instance of line reduction. lith_result, lith_details = lith_reduce([]) if lith_details is not None: # lith_details can be None if testcase no longer becomes interesting origNumOfLines = int(lith_details.split()[0]) # pylint: disable=invalid-name hasTryItOut = False # pylint: disable=invalid-name hasTryItOutRegex = re.compile(r'count=[0-9]+; tryItOut\("') # pylint: disable=invalid-name with io.open(str(infilename), "r", encoding="utf-8", errors="replace") as f: for line in file_manipulation.linesWith(f, '; tryItOut("'): # Checks if testcase came from jsfunfuzz or compare_jit. # 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) # pylint: disable=invalid-name 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 lith_result == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: tryItOutAndCountRegex = re.compile(r'"\);\ncount=([0-9]+); tryItOut\("', # pylint: disable=invalid-name re.MULTILINE) with io.open(str(infilename), "r", encoding="utf-8", errors="replace") as f: infileContents = f.read() # pylint: disable=invalid-name infileContents = re.sub(tryItOutAndCountRegex, # pylint: disable=invalid-name ';\\\n"); count=\\1; tryItOut("\\\n', infileContents) with io.open(str(infilename), "w", encoding="utf-8", errors="replace") as f: f.write(infileContents) print() print("Running 1 instance of 1-line reduction after moving tryItOut and count=X...") print() # --chunksize=1: Reduce only individual lines, for only 1 round. lith_result, lith_details = lith_reduce(["--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 lith_result == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: intendedLines = [] # pylint: disable=invalid-name with io.open(str(infilename), "r", encoding="utf-8", errors="replace") 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")) with io.open(str(infilename), "w", encoding="utf-8", errors="replace") as f: f.writelines(intendedLines) print() print("Running 1 instance of 2-line reduction after moving count=X to its own line...") print() lith_result, lith_details = lith_reduce(["--chunksize=2"]) # Step 4: Run 1 instance of 2-line reduction again, e.g. to remove pairs of STRICT_MODE lines. if lith_result == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: print() print("Running 1 instance of 2-line reduction again...") print() lith_result, lith_details = lith_reduce(["--chunksize=2"]) isLevOverallMismatchAsmJsAvailable = (lev == JS_OVERALL_MISMATCH and # pylint: disable=invalid-name file_contains_str(str(infilename), "isAsmJSCompilationAvailable")) # Step 5 (not always run): Run character reduction within interesting lines. if lith_result == LITH_FINISHED and origNumOfLines <= 50 and targetTime is None and \ lev >= JS_OVERALL_MISMATCH and not isLevOverallMismatchAsmJsAvailable: print() print("Running character reduction...") print() lith_result, lith_details = lith_reduce(["--char"]) # Step 6: Run line reduction after activating SECOND DDBEGIN with a 1-line offset. if lith_result == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: infileContents = [] # pylint: disable=invalid-name with io.open(str(infilename), "r", encoding="utf-8", errors="replace") 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 io.open(str(infilename), "w", encoding="utf-8", errors="replace") as f: f.writelines(infileContents) print() print("Running line reduction with a 1-line offset...") print() lith_result, lith_details = lith_reduce([]) # Step 7: Run line reduction for a final time. if lith_result == LITH_FINISHED and origNumOfLines <= 50 and hasTryItOut and lev >= JS_VG_AMISS: print() print("Running the final line reduction...") print() lith_result, lith_details = lith_reduce([]) # Restore from backup if testcase can no longer be reproduced halfway through reduction. if lith_result != LITH_FINISHED: # Probably can move instead of copy the backup, once this has stabilised. if backup_file.is_file(): shutil.copy2(str(backup_file), str(infilename)) else: print("DEBUG! backup_file is supposed to be: %s" % backup_file) return lith_result, lith_details