Index: utils/analyzer/CmpRuns.py =================================================================== --- utils/analyzer/CmpRuns.py +++ utils/analyzer/CmpRuns.py @@ -6,8 +6,8 @@ This is designed to support automated testing using the static analyzer, from two perspectives: - 1. To monitor changes in the static analyzer's reports on real code bases, for - regression testing. + 1. To monitor changes in the static analyzer's reports on real code bases, + for regression testing. 2. For use by end users who want to integrate regular static analyzer testing into a buildbot like environment. @@ -29,6 +29,7 @@ import os import plistlib + # Information about analysis run: # path - the analysis output directory # root - the name of the root directory, which will be disregarded when @@ -39,6 +40,7 @@ self.root = root.rstrip("/\\") self.verboseLog = verboseLog + class AnalysisDiagnostic: def __init__(self, data, report, htmlReport): self._data = data @@ -50,7 +52,7 @@ root = self._report.run.root fileName = self._report.files[self._loc['file']] if fileName.startswith(root) and len(root) > 0: - return fileName[len(root)+1:] + return fileName[len(root) + 1:] return fileName def getLine(self): @@ -65,12 +67,12 @@ def getDescription(self): return self._data['description'] - def getIssueIdentifier(self) : + def getIssueIdentifier(self): id = self.getFileName() + "+" - if 'issue_context' in self._data : - id += self._data['issue_context'] + "+" - if 'issue_hash_content_of_line_in_context' in self._data : - id += str(self._data['issue_hash_content_of_line_in_context']) + if 'issue_context' in self._data: + id += self._data['issue_context'] + "+" + if 'issue_hash_content_of_line_in_context' in self._data: + id += str(self._data['issue_hash_content_of_line_in_context']) return id def getReport(self): @@ -88,18 +90,21 @@ def getRawData(self): return self._data + class CmpOptions: def __init__(self, verboseLog=None, rootA="", rootB=""): self.rootA = rootA self.rootB = rootB self.verboseLog = verboseLog + class AnalysisReport: def __init__(self, run, files): self.run = run self.files = files self.diagnostics = [] + class AnalysisRun: def __init__(self, info): self.path = info.path @@ -120,14 +125,14 @@ # reports. Assume that all reports were created using the same # clang version (this is always true and is more efficient). if 'clang_version' in data: - if self.clang_version == None: + if self.clang_version is None: self.clang_version = data.pop('clang_version') else: data.pop('clang_version') # Ignore/delete empty reports. if not data['files']: - if deleteEmpty == True: + if deleteEmpty: os.remove(p) return @@ -144,8 +149,7 @@ report = AnalysisReport(self, data.pop('files')) diagnostics = [AnalysisDiagnostic(d, report, h) - for d,h in zip(data.pop('diagnostics'), - htmlFiles)] + for d, h in zip(data.pop('diagnostics'), htmlFiles)] assert not data @@ -154,15 +158,21 @@ self.diagnostics.extend(diagnostics) -# Backward compatibility API. def loadResults(path, opts, root = "", deleteEmpty=True): + """ + Backwards compatibility API. + """ return loadResultsFromSingleRun(SingleRunInfo(path, root, opts.verboseLog), deleteEmpty) -# Load results of the analyzes from a given output folder. -# - info is the SingleRunInfo object -# - deleteEmpty specifies if the empty plist files should be deleted + def loadResultsFromSingleRun(info, deleteEmpty=True): + """ + # Load results of the analyzes from a given output folder. + # - info is the SingleRunInfo object + # - deleteEmpty specifies if the empty plist files should be deleted + + """ path = info.path run = AnalysisRun(info) @@ -178,9 +188,11 @@ return run -def cmpAnalysisDiagnostic(d) : + +def cmpAnalysisDiagnostic(d): return d.getIssueIdentifier() + def compareResults(A, B): """ compareResults - Generate a relation from diagnostics in run A to @@ -199,12 +211,12 @@ neqB = [] eltsA = list(A.diagnostics) eltsB = list(B.diagnostics) - eltsA.sort(key = cmpAnalysisDiagnostic) - eltsB.sort(key = cmpAnalysisDiagnostic) + eltsA.sort(key=cmpAnalysisDiagnostic) + eltsB.sort(key=cmpAnalysisDiagnostic) while eltsA and eltsB: a = eltsA.pop() b = eltsB.pop() - if (a.getIssueIdentifier() == b.getIssueIdentifier()) : + if (a.getIssueIdentifier() == b.getIssueIdentifier()): res.append((a, b, 0)) elif a.getIssueIdentifier() > b.getIssueIdentifier(): eltsB.append(b) @@ -215,11 +227,11 @@ neqA.extend(eltsA) neqB.extend(eltsB) - # FIXME: Add fuzzy matching. One simple and possible effective idea would be - # to bin the diagnostics, print them in a normalized form (based solely on - # the structure of the diagnostic), compute the diff, then use that as the - # basis for matching. This has the nice property that we don't depend in any - # way on the diagnostic format. + # FIXME: Add fuzzy matching. One simple and possible effective idea would + # be to bin the diagnostics, print them in a normalized form (based solely + # on the structure of the diagnostic), compute the diff, then use that as + # the basis for matching. This has the nice property that we don't depend + # in any way on the diagnostic format. for a in neqA: res.append((a, None, None)) @@ -228,6 +240,7 @@ return res + def dumpScanBuildResultsDiff(dirA, dirB, opts, deleteEmpty=True): # Load the run results. resultsA = loadResults(dirA, opts, opts.rootA, deleteEmpty) @@ -242,7 +255,7 @@ diff = compareResults(resultsA, resultsB) foundDiffs = 0 for res in diff: - a,b,confidence = res + a, b, confidence = res if a is None: print "ADDED: %r" % b.getReadableName() foundDiffs += 1 @@ -277,6 +290,7 @@ return foundDiffs, len(resultsA.diagnostics), len(resultsB.diagnostics) + def main(): from optparse import OptionParser parser = OptionParser("usage: %prog [options] [dir A] [dir B]") @@ -287,7 +301,8 @@ help="Prefix to ignore on source files for directory B", action="store", type=str, default="") parser.add_option("", "--verbose-log", dest="verboseLog", - help="Write additional information to LOG [default=None]", + help="Write additional information to LOG \ + [default=None]", action="store", type=str, default=None, metavar="LOG") (opts, args) = parser.parse_args() @@ -295,9 +310,10 @@ if len(args) != 2: parser.error("invalid number of arguments") - dirA,dirB = args + dirA, dirB = args dumpScanBuildResultsDiff(dirA, dirB, opts) + if __name__ == '__main__': main() Index: utils/analyzer/SATestAdd.py =================================================================== --- utils/analyzer/SATestAdd.py +++ utils/analyzer/SATestAdd.py @@ -27,15 +27,17 @@ - CachedSource/ - An optional directory containing the source of the project being analyzed. If present, download_project.sh will not be called. - - changes_for_analyzer.patch - An optional patch file for any local changes + - changes_for_analyzer.patch - An optional patch file for any local + changes (e.g., to adapt to newer version of clang) that should be applied to CachedSource before analysis. To construct this patch, run the the download script to download the project to CachedSource, copy the CachedSource to another directory (for - example, PatchedSource) and make any needed - modifications to the the copied source. + example, PatchedSource) and make any + needed modifications to the the copied + source. Then run: diff -ur CachedSource PatchedSource \ > changes_for_analyzer.patch @@ -46,18 +48,21 @@ import csv import sys -def isExistingProject(PMapFile, projectID) : + +def isExistingProject(PMapFile, projectID): PMapReader = csv.reader(PMapFile) for I in PMapReader: if projectID == I[0]: return True return False -# Add a new project for testing: build it and add to the Project Map file. -# Params: -# Dir is the directory where the sources are. -# ID is a short string used to identify a project. -def addNewProject(ID, BuildMode) : + +def addNewProject(ID, BuildMode): + """ + Add a new project for testing: build it and add to the Project Map file. + :param ID: is a short string used to identify a project. + """ + CurDir = os.path.abspath(os.curdir) Dir = SATestBuild.getProjectDir(ID) if not os.path.exists(Dir): @@ -69,29 +74,29 @@ # Add the project ID to the project map. ProjectMapPath = os.path.join(CurDir, SATestBuild.ProjectMapFile) + if os.path.exists(ProjectMapPath): - PMapFile = open(ProjectMapPath, "r+b") + FileMode = "r+b" else: print "Warning: Creating the Project Map file!!" - PMapFile = open(ProjectMapPath, "w+b") - try: - if (isExistingProject(PMapFile, ID)) : + FileMode = "w+b" + + with open(ProjectMapPath, FileMode) as PMapFile: + if (isExistingProject(PMapFile, ID)): print >> sys.stdout, 'Warning: Project with ID \'', ID, \ '\' already exists.' print >> sys.stdout, "Reference output has been regenerated." else: PMapWriter = csv.writer(PMapFile) - PMapWriter.writerow( (ID, int(BuildMode)) ); + PMapWriter.writerow((ID, int(BuildMode))) print "The project map is updated: ", ProjectMapPath - finally: - PMapFile.close() # TODO: Add an option not to build. # TODO: Set the path to the Repository directory. if __name__ == '__main__': if len(sys.argv) < 2 or sys.argv[1] in ('-h', '--help'): - print >> sys.stderr, 'Add a new project for testing to static analyzer'\ + print >> sys.stderr, 'Add a new project for testing to the analyzer'\ '\nUsage: ', sys.argv[0],\ 'project_ID \n' \ 'mode: 0 for single file project, ' \ Index: utils/analyzer/SATestBuild.py =================================================================== --- utils/analyzer/SATestBuild.py +++ utils/analyzer/SATestBuild.py @@ -3,8 +3,8 @@ """ Static Analyzer qualification infrastructure. -The goal is to test the analyzer against different projects, check for failures, -compare results, and measure performance. +The goal is to test the analyzer against different projects, +check for failures, compare results, and measure performance. Repository Directory will contain sources of the projects as well as the information on how to build them and the expected output. @@ -20,7 +20,8 @@ To test the build of the analyzer one would: - Copy over a copy of the Repository Directory. (TODO: Prefer to ensure that - the build directory does not pollute the repository to min network traffic). + the build directory does not pollute the repository to min network + traffic). - Build all projects, until error. Produce logs to report errors. - Compare results. @@ -60,69 +61,78 @@ #------------------------------------------------------------------------------ -def which(command, paths = None): - """which(command, [paths]) - Look up the given command in the paths string - (or the PATH environment variable, if unspecified).""" +def which(command, paths=None): + """which(command, [paths]) - Look up the given command in the paths string + (or the PATH environment variable, if unspecified).""" - if paths is None: - paths = os.environ.get('PATH','') + if paths is None: + paths = os.environ.get('PATH', '') - # Check for absolute match first. - if os.path.exists(command): - return command + # Check for absolute match first. + if os.path.exists(command): + return command - # Would be nice if Python had a lib function for this. - if not paths: - paths = os.defpath + # Would be nice if Python had a lib function for this. + if not paths: + paths = os.defpath - # Get suffixes to search. - # On Cygwin, 'PATHEXT' may exist but it should not be used. - if os.pathsep == ';': - pathext = os.environ.get('PATHEXT', '').split(';') - else: - pathext = [''] + # Get suffixes to search. + # On Cygwin, 'PATHEXT' may exist but it should not be used. + if os.pathsep == ';': + pathext = os.environ.get('PATHEXT', '').split(';') + else: + pathext = [''] + + # Search the paths... + for path in paths.split(os.pathsep): + for ext in pathext: + p = os.path.join(path, command + ext) + if os.path.exists(p): + return p - # Search the paths... - for path in paths.split(os.pathsep): - for ext in pathext: - p = os.path.join(path, command + ext) - if os.path.exists(p): - return p + return None - return None -# Make sure we flush the output after every print statement. class flushfile(object): + """ + Wrapper to flush the output after every print statement. + """ def __init__(self, f): self.f = f + def write(self, x): self.f.write(x) self.f.flush() + sys.stdout = flushfile(sys.stdout) + def getProjectMapPath(): ProjectMapPath = os.path.join(os.path.abspath(os.curdir), ProjectMapFile) if not os.path.exists(ProjectMapPath): print "Error: Cannot find the Project Map file " + ProjectMapPath +\ - "\nRunning script for the wrong directory?" + "\nRunning script for the wrong directory?" sys.exit(-1) return ProjectMapPath + def getProjectDir(ID): return os.path.join(os.path.abspath(os.curdir), ID) -def getSBOutputDirName(IsReferenceBuild) : - if IsReferenceBuild == True : + +def getSBOutputDirName(IsReferenceBuild): + if IsReferenceBuild: return SBOutputDirReferencePrefix + SBOutputDirName - else : + else: return SBOutputDirName #------------------------------------------------------------------------------ # Configuration setup. #------------------------------------------------------------------------------ + # Find Clang for static analysis. if 'CC' in os.environ: Clang = os.environ['CC'] @@ -160,9 +170,10 @@ SBOutputDirName = "ScanBuildResults" SBOutputDirReferencePrefix = "Ref" -# The name of the directory storing the cached project source. If this directory -# does not exist, the download script will be executed. That script should -# create the "CachedSource" directory and download the project source into it. +# The name of the directory storing the cached project source. If this +# directory does not exist, the download script will be executed. +# That script should create the "CachedSource" directory and download the +# project source into it. CachedSourceDirName = "CachedSource" # The name of the directory containing the source code that will be analyzed. @@ -178,7 +189,16 @@ # The list of checkers used during analyzes. # Currently, consists of all the non-experimental checkers, plus a few alpha # checkers we don't want to regress on. -Checkers="alpha.unix.SimpleStream,alpha.security.taint,cplusplus.NewDeleteLeaks,core,cplusplus,deadcode,security,unix,osx" +Checkers = ",".join[ + "alpha.unix.SimpleStream", + "alpha.security.taint", + "cplusplus.NewDeleteLeaks", + "core", + "cplusplus", + "deadcode", + "security", + "unix", + "osx"] Verbose = 1 @@ -186,46 +206,60 @@ # Test harness logic. #------------------------------------------------------------------------------ -# Run pre-processing script if any. + def runCleanupScript(Dir, PBuildLogFile): + """ + Run pre-processing script if any. + """ Cwd = os.path.join(Dir, PatchedSourceDirName) ScriptPath = os.path.join(Dir, CleanupScript) runScript(ScriptPath, PBuildLogFile, Cwd) -# Run the script to download the project, if it exists. + def runDownloadScript(Dir, PBuildLogFile): + """ + Run the script to download the project, if it exists. + """ ScriptPath = os.path.join(Dir, DownloadScript) runScript(ScriptPath, PBuildLogFile, Dir) -# Run the provided script if it exists. + def runScript(ScriptPath, PBuildLogFile, Cwd): + """ + Run the provided script if it exists. + """ if os.path.exists(ScriptPath): try: if Verbose == 1: print " Executing: %s" % (ScriptPath,) - check_call("chmod +x '%s'" % ScriptPath, cwd = Cwd, - stderr=PBuildLogFile, - stdout=PBuildLogFile, - shell=True) - check_call("'%s'" % ScriptPath, cwd = Cwd, stderr=PBuildLogFile, - stdout=PBuildLogFile, - shell=True) + check_call("chmod +x '%s'" % ScriptPath, cwd=Cwd, + stderr=PBuildLogFile, + stdout=PBuildLogFile, + shell=True) + check_call("'%s'" % ScriptPath, cwd=Cwd, + stderr=PBuildLogFile, + stdout=PBuildLogFile, + shell=True) except: - print "Error: Running %s failed. See %s for details." % (ScriptPath, - PBuildLogFile.name) + print "Error: Running %s failed. See %s for details." % ( + ScriptPath, PBuildLogFile.name) sys.exit(-1) -# Download the project and apply the local patchfile if it exists. + def downloadAndPatch(Dir, PBuildLogFile): + """ + Download the project and apply the local patchfile if it exists. + """ CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName) # If the we don't already have the cached source, run the project's # download script to download it. if not os.path.exists(CachedSourceDirPath): - runDownloadScript(Dir, PBuildLogFile) - if not os.path.exists(CachedSourceDirPath): - print "Error: '%s' not found after download." % (CachedSourceDirPath) - exit(-1) + runDownloadScript(Dir, PBuildLogFile) + if not os.path.exists(CachedSourceDirPath): + print "Error: '%s' not found after download." % ( + CachedSourceDirPath) + exit(-1) PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName) @@ -237,6 +271,7 @@ shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True) applyPatch(Dir, PBuildLogFile) + def applyPatch(Dir, PBuildLogFile): PatchfilePath = os.path.join(Dir, PatchfileName) PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName) @@ -247,30 +282,33 @@ print " Applying patch." try: check_call("patch -p1 < '%s'" % (PatchfilePath), - cwd = PatchedSourceDirPath, - stderr=PBuildLogFile, - stdout=PBuildLogFile, - shell=True) + cwd=PatchedSourceDirPath, + stderr=PBuildLogFile, + stdout=PBuildLogFile, + shell=True) except: print "Error: Patch failed. See %s for details." % (PBuildLogFile.name) sys.exit(-1) -# Build the project with scan-build by reading in the commands and -# prefixing them with the scan-build options. + def runScanBuild(Dir, SBOutputDir, PBuildLogFile): + """ + Build the project with scan-build by reading in the commands and + prefixing them with the scan-build options. + """ BuildScriptPath = os.path.join(Dir, BuildScript) if not os.path.exists(BuildScriptPath): print "Error: build script is not defined: %s" % BuildScriptPath sys.exit(-1) AllCheckers = Checkers - if os.environ.has_key('SA_ADDITIONAL_CHECKERS'): + if 'SA_ADDITIONAL_CHECKERS' in os.environ: AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS'] # Run scan-build from within the patched source directory. SBCwd = os.path.join(Dir, PatchedSourceDirName) - SBOptions = "--use-analyzer '%s' " % Clang + SBOptions = "--use-analyzer '%s' " % Clang SBOptions += "-plist-html -o '%s' " % SBOutputDir SBOptions += "-enable-checker " + AllCheckers + " " SBOptions += "--keep-empty " @@ -283,7 +321,7 @@ for Command in SBCommandFile: Command = Command.strip() if len(Command) == 0: - continue; + continue # If using 'make', auto imply a -jX argument # to speed up analysis. xcodebuild will # automatically use the maximum number of cores. @@ -293,42 +331,45 @@ SBCommand = SBPrefix + Command if Verbose == 1: print " Executing: %s" % (SBCommand,) - check_call(SBCommand, cwd = SBCwd, stderr=PBuildLogFile, - stdout=PBuildLogFile, - shell=True) + check_call(SBCommand, cwd=SBCwd, + stderr=PBuildLogFile, + stdout=PBuildLogFile, + shell=True) except: - print "Error: scan-build failed. See ",PBuildLogFile.name,\ + print "Error: scan-build failed. See ", PBuildLogFile.name,\ " for details." raise + def hasNoExtension(FileName): (Root, Ext) = os.path.splitext(FileName) - if ((Ext == "")) : - return True - return False + return (Ext == "") + def isValidSingleInputFile(FileName): (Root, Ext) = os.path.splitext(FileName) - if ((Ext == ".i") | (Ext == ".ii") | - (Ext == ".c") | (Ext == ".cpp") | - (Ext == ".m") | (Ext == "")) : - return True - return False - -# Get the path to the SDK for the given SDK name. Returns None if -# the path cannot be determined. + return Ext in (".i", ".ii", ".c", ".cpp", ".m", "") + + def getSDKPath(SDKName): + """ + Get the path to the SDK for the given SDK name. Returns None if + the path cannot be determined. + """ if which("xcrun") is None: return None Cmd = "xcrun --sdk " + SDKName + " --show-sdk-path" return check_output(Cmd, shell=True).rstrip() -# Run analysis on a set of preprocessed files. + def runAnalyzePreprocessed(Dir, SBOutputDir, Mode): + """ + Run analysis on a set of preprocessed files. + """ if os.path.exists(os.path.join(Dir, BuildScript)): print "Error: The preprocessed files project should not contain %s" % \ - BuildScript + BuildScript raise Exception() CmdPrefix = Clang + " -cc1 " @@ -337,17 +378,18 @@ # with the OS X SDK. SDKPath = getSDKPath("macosx") if SDKPath is not None: - CmdPrefix += "-isysroot " + SDKPath + " " + CmdPrefix += "-isysroot " + SDKPath + " " CmdPrefix += "-analyze -analyzer-output=plist -w " - CmdPrefix += "-analyzer-checker=" + Checkers +" -fcxx-exceptions -fblocks " + CmdPrefix += "-analyzer-checker=" + Checkers + CmdPrefix += " -fcxx-exceptions -fblocks " - if (Mode == 2) : + if (Mode == 2): CmdPrefix += "-std=c++11 " PlistPath = os.path.join(Dir, SBOutputDir, "date") - FailPath = os.path.join(PlistPath, "failures"); - os.makedirs(FailPath); + FailPath = os.path.join(PlistPath, "failures") + os.makedirs(FailPath) for FullFileName in glob.glob(Dir + "/*"): FileName = os.path.basename(FullFileName) @@ -356,7 +398,7 @@ # Only run the analyzes on supported files. if (hasNoExtension(FileName)): continue - if (isValidSingleInputFile(FileName) == False): + if (not isValidSingleInputFile(FileName)): print "Error: Invalid single input file %s." % (FullFileName,) raise Exception() @@ -367,44 +409,47 @@ try: if Verbose == 1: print " Executing: %s" % (Command,) - check_call(Command, cwd = Dir, stderr=LogFile, - stdout=LogFile, - shell=True) + check_call(Command, cwd=Dir, stderr=LogFile, + stdout=LogFile, + shell=True) except CalledProcessError, e: print "Error: Analyzes of %s failed. See %s for details." \ - "Error code %d." % \ - (FullFileName, LogFile.name, e.returncode) + "Error code %d." % ( + FullFileName, LogFile.name, e.returncode) Failed = True finally: LogFile.close() # If command did not fail, erase the log file. - if Failed == False: - os.remove(LogFile.name); + if not Failed: + os.remove(LogFile.name) + def getBuildLogPath(SBOutputDir): - return os.path.join(SBOutputDir, LogFolderName, BuildLogName) + return os.path.join(SBOutputDir, LogFolderName, BuildLogName) + def removeLogFile(SBOutputDir): - BuildLogPath = getBuildLogPath(SBOutputDir) - # Clean up the log file. - if (os.path.exists(BuildLogPath)) : - RmCommand = "rm '%s'" % BuildLogPath - if Verbose == 1: - print " Executing: %s" % (RmCommand,) - check_call(RmCommand, shell=True) + BuildLogPath = getBuildLogPath(SBOutputDir) + # Clean up the log file. + if (os.path.exists(BuildLogPath)): + RmCommand = "rm '%s'" % BuildLogPath + if Verbose == 1: + print " Executing: %s" % (RmCommand,) + check_call(RmCommand, shell=True) + def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild): TBegin = time.time() BuildLogPath = getBuildLogPath(SBOutputDir) print "Log file: %s" % (BuildLogPath,) - print "Output directory: %s" %(SBOutputDir, ) + print "Output directory: %s" % (SBOutputDir, ) removeLogFile(SBOutputDir) # Clean up scan build results. - if (os.path.exists(SBOutputDir)) : + if (os.path.exists(SBOutputDir)): RmCommand = "rm -r '%s'" % SBOutputDir if Verbose == 1: print " Executing: %s" % (RmCommand,) @@ -426,7 +471,8 @@ normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode) print "Build complete (time: %.2f). See the log for more details: %s" % \ - ((time.time()-TBegin), BuildLogPath) + ((time.time() - TBegin), BuildLogPath) + def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode): """ @@ -441,15 +487,19 @@ PathPrefix = Dir if (ProjectBuildMode == 1): PathPrefix = os.path.join(Dir, PatchedSourceDirName) - Paths = [SourceFile[len(PathPrefix)+1:]\ - if SourceFile.startswith(PathPrefix)\ - else SourceFile for SourceFile in Data['files']] + Paths = [SourceFile[len(PathPrefix) + 1:] + if SourceFile.startswith(PathPrefix) + else SourceFile for SourceFile in Data['files']] Data['files'] = Paths plistlib.writePlist(Data, Plist) -# A plist file is created for each call to the analyzer(each source file). -# We are only interested on the once that have bug reports, so delete the rest. + def CleanUpEmptyPlists(SBOutputDir): + """ + A plist file is created for each call to the analyzer(each source file). + We are only interested on the once that have bug reports, + so delete the rest. + """ for F in glob.glob(SBOutputDir + "/*/*.plist"): P = os.path.join(SBOutputDir, F) @@ -459,55 +509,66 @@ os.remove(P) continue -# Given the scan-build output directory, checks if the build failed -# (by searching for the failures directories). If there are failures, it -# creates a summary file in the output directory. + def checkBuild(SBOutputDir): + """ + Given the scan-build output directory, checks if the build failed + (by searching for the failures directories). If there are failures, it + creates a summary file in the output directory. + + """ # Check if there are failures. Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt") - TotalFailed = len(Failures); + TotalFailed = len(Failures) if TotalFailed == 0: CleanUpEmptyPlists(SBOutputDir) Plists = glob.glob(SBOutputDir + "/*/*.plist") print "Number of bug reports (non-empty plist files) produced: %d" %\ - len(Plists) - return; + len(Plists) + return # Create summary file to display when the build fails. - SummaryPath = os.path.join(SBOutputDir, LogFolderName, FailuresSummaryFileName) + SummaryPath = os.path.join( + SBOutputDir, LogFolderName, FailuresSummaryFileName) if (Verbose > 0): print " Creating the failures summary file %s" % (SummaryPath,) with open(SummaryPath, "w+") as SummaryLog: SummaryLog.write("Total of %d failures discovered.\n" % (TotalFailed,)) if TotalFailed > NumOfFailuresInSummary: - SummaryLog.write("See the first %d below.\n" - % (NumOfFailuresInSummary,)) + SummaryLog.write("See the first %d below.\n" % ( + NumOfFailuresInSummary,)) # TODO: Add a line "See the results folder for more." Idx = 0 for FailLogPathI in Failures: if Idx >= NumOfFailuresInSummary: - break; + break Idx += 1 - SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,)); + SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,)) with open(FailLogPathI, "r") as FailLogI: - shutil.copyfileobj(FailLogI, SummaryLog); + shutil.copyfileobj(FailLogI, SummaryLog) print "Error: analysis failed. See ", SummaryPath sys.exit(-1) -# Auxiliary object to discard stdout. + class Discarder(object): + """ + Auxiliary object to discard stdout. + """ def write(self, text): - pass # do nothing - -# Compare the warnings produced by scan-build. -# Strictness defines the success criteria for the test: -# 0 - success if there are no crashes or analyzer failure. -# 1 - success if there are no difference in the number of reported bugs. -# 2 - success if all the bug reports are identical. -def runCmpResults(Dir, Strictness = 0): + pass # do nothing + + +def runCmpResults(Dir, Strictness=0): + """ + Compare the warnings produced by scan-build. + Strictness defines the success criteria for the test: + 0 - success if there are no crashes or analyzer failure. + 1 - success if there are no difference in the number of reported bugs. + 2 - success if all the bug reports are identical. + """ TBegin = time.time() RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName) @@ -548,29 +609,35 @@ DiffsPath = os.path.join(NewDir, DiffsSummaryFileName) PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName) Opts = CmpRuns.CmpOptions(DiffsPath, "", PatchedSourceDirPath) - # Discard everything coming out of stdout (CmpRun produces a lot of them). + # Discard everything coming out of stdout + # (CmpRun produces a lot of them). OLD_STDOUT = sys.stdout sys.stdout = Discarder() # Scan the results, delete empty plist files. NumDiffs, ReportsInRef, ReportsInNew = \ CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False) sys.stdout = OLD_STDOUT - if (NumDiffs > 0) : + if (NumDiffs > 0): print "Warning: %r differences in diagnostics. See %s" % \ (NumDiffs, DiffsPath,) if Strictness >= 2 and NumDiffs > 0: print "Error: Diffs found in strict mode (2)." sys.exit(-1) elif Strictness >= 1 and ReportsInRef != ReportsInNew: - print "Error: The number of results are different in strict mode (1)." + print "Error: The number of results are different in "\ + "strict mode (1)." sys.exit(-1) - print "Diagnostic comparison complete (time: %.2f)." % (time.time()-TBegin) + print "Diagnostic comparison complete (time: %.2f)." % ( + time.time() - TBegin) return (NumDiffs > 0) + def cleanupReferenceResults(SBOutputDir): - # Delete html, css, and js files from reference results. These can - # include multiple copies of the benchmark source and so get very large. + """ + Delete html, css, and js files from reference results. These can + include multiple copies of the benchmark source and so get very large. + """ Extensions = ["html", "css", "js"] for E in Extensions: for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)): @@ -581,6 +648,7 @@ # Remove the log file. It leaks absolute path names. removeLogFile(SBOutputDir) + def updateSVN(Mode, PMapFile): """ svn delete or svn add (depending on `Mode`) all folders defined in the file @@ -614,7 +682,8 @@ print "Error: SVN update failed." sys.exit(-1) -def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness = 0): + +def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0): print " \n\n--- Building project %s" % (ID,) TBegin = time.time() @@ -637,72 +706,80 @@ runCmpResults(Dir, Strictness) print "Completed tests for project %s (time: %.2f)." % \ - (ID, (time.time()-TBegin)) + (ID, (time.time() - TBegin)) + def isCommentCSVLine(Entries): - # Treat CSV lines starting with a '#' as a comment. + """ + Treat CSV lines starting with a '#' as a comment. + """ return len(Entries) > 0 and Entries[0].startswith("#") + def projectFileHandler(): return open(getProjectMapPath(), "rb") + def iterateOverProjects(PMapFile): """ Iterate over all projects defined in the project file handler `PMapFile` from the start. """ PMapFile.seek(0) - try: - for I in csv.reader(PMapFile): - if (isCommentCSVLine(I)): - continue - yield I - except: - print "Error occurred. Premature termination." - raise + for I in csv.reader(PMapFile): + if (isCommentCSVLine(I)): + continue + yield I + def validateProjectFile(PMapFile): """ Validate project file. """ for I in iterateOverProjects(PMapFile): - if (len(I) != 2) : + if (len(I) != 2): print "Error: Rows in the ProjectMapFile should have 2 entries." raise Exception() if (not ((I[1] == "0") | (I[1] == "1") | (I[1] == "2"))): print "Error: Second entry in the ProjectMapFile should be 0" \ - " (single file), 1 (project), or 2(single file c++11)." + " (single file), 1 (project), or 2(single file c++11)." raise Exception() -def testAll(IsReferenceBuild = False, UpdateSVN = False, Strictness = 0): + +def testAll(IsReferenceBuild=False, UpdateSVN=False, Strictness=0): with projectFileHandler() as PMapFile: validateProjectFile(PMapFile) # When we are regenerating the reference results, we might need to # update svn. Remove reference results from SVN. - if UpdateSVN == True: - assert(IsReferenceBuild == True); - updateSVN("delete", PMapFile); + if UpdateSVN: + assert(IsReferenceBuild) + updateSVN("delete", PMapFile) # Test the projects. for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile): - testProject(ProjName, int(ProjBuildMode), IsReferenceBuild, Strictness) + testProject( + ProjName, int(ProjBuildMode), IsReferenceBuild, Strictness) # Re-add reference results to SVN. - if UpdateSVN == True: - updateSVN("add", PMapFile); + if UpdateSVN: + updateSVN("add", PMapFile) + if __name__ == '__main__': # Parse command line arguments. - Parser = argparse.ArgumentParser(description='Test the Clang Static Analyzer.') + Parser = argparse.ArgumentParser( + description='Test the Clang Static Analyzer.') Parser.add_argument('--strictness', dest='strictness', type=int, default=0, - help='0 to fail on runtime errors, 1 to fail when the number\ - of found bugs are different from the reference, 2 to \ - fail on any difference from the reference. Default is 0.') - Parser.add_argument('-r', dest='regenerate', action='store_true', default=False, - help='Regenerate reference output.') + help='0 to fail on runtime errors, 1 to fail when the \ + number of found bugs are different from the \ + reference, 2 to fail on any difference from the \ + reference. Default is 0.') + Parser.add_argument('-r', dest='regenerate', action='store_true', + default=False, help='Regenerate reference output.') Parser.add_argument('-rs', dest='update_reference', action='store_true', - default=False, help='Regenerate reference output and update svn.') + default=False, + help='Regenerate reference output and update svn.') Args = Parser.parse_args() IsReference = False Index: utils/analyzer/SATestUpdateDiffs.py =================================================================== --- utils/analyzer/SATestUpdateDiffs.py +++ utils/analyzer/SATestUpdateDiffs.py @@ -11,18 +11,23 @@ import sys Verbose = 1 + + def runCmd(Command): if Verbose: print "Executing %s" % Command check_call(Command, shell=True) + def updateReferenceResults(ProjName, ProjBuildMode): ProjDir = SATestBuild.getProjectDir(ProjName) - RefResultsPath = os.path.join(ProjDir, - SATestBuild.getSBOutputDirName(IsReferenceBuild=True)) - CreatedResultsPath = os.path.join(ProjDir, - SATestBuild.getSBOutputDirName(IsReferenceBuild=False)) + RefResultsPath = os.path.join( + ProjDir, + SATestBuild.getSBOutputDirName(IsReferenceBuild=True)) + CreatedResultsPath = os.path.join( + ProjDir, + SATestBuild.getSBOutputDirName(IsReferenceBuild=False)) if not os.path.exists(CreatedResultsPath): print >> sys.stderr, "New results not found, was SATestBuild.py "\ @@ -36,29 +41,34 @@ runCmd('cp -r "%s" "%s"' % (CreatedResultsPath, RefResultsPath,)) # Run cleanup script. - with open(SATestBuild.getBuildLogPath(RefResultsPath), "wb+") as PBuildLogFile: + BuildLogPath = SATestBuild.getBuildLogPath(RefResultsPath) + with open(BuildLogPath, "wb+") as PBuildLogFile: SATestBuild.runCleanupScript(ProjDir, PBuildLogFile) - SATestBuild.normalizeReferenceResults(ProjDir, RefResultsPath, ProjBuildMode) + SATestBuild.normalizeReferenceResults( + ProjDir, RefResultsPath, ProjBuildMode) # Clean up the generated difference results. SATestBuild.cleanupReferenceResults(RefResultsPath) # Remove the created .diffs file before adding. - runCmd('rm -f "%s/*/%s"' % (RefResultsPath, SATestBuild.DiffsSummaryFileName)) + runCmd('rm -f "%s/*/%s"' % ( + RefResultsPath, SATestBuild.DiffsSummaryFileName)) runCmd('git add "%s"' % (RefResultsPath,)) + def main(argv): if len(argv) == 2 and argv[1] in ('-h', '--help'): print >> sys.stderr, "Update static analyzer reference results based "\ "\non the previous run of SATestBuild.py.\n"\ - "\nN.B.: Assumes that SATestBuild.py was just run." + "\nN.B.: Assumes that SATestBuild.py was just run" sys.exit(-1) with SATestBuild.projectFileHandler() as f: for (ProjName, ProjBuildMode) in SATestBuild.iterateOverProjects(f): updateReferenceResults(ProjName, int(ProjBuildMode)) + if __name__ == '__main__': main(sys.argv) Index: utils/analyzer/SumTimerInfo.py =================================================================== --- utils/analyzer/SumTimerInfo.py +++ utils/analyzer/SumTimerInfo.py @@ -5,11 +5,8 @@ Statistics are enabled by passing '-internal-stats' option to scan-build (or '-analyzer-stats' to the analyzer). - """ -import string -from operator import itemgetter import sys if __name__ == '__main__': @@ -31,44 +28,42 @@ NumInlinedCallSites = 0 NumBifurcatedCallSites = 0 MaxCFGSize = 0 - Mode = 1 for line in f: - if ("Miscellaneous Ungrouped Timers" in line) : - Mode = 1 - if (("Analyzer Total Time" in line) and (Mode == 1)) : - s = line.split() - Time = Time + float(s[6]) - Count = Count + 1 - if (float(s[6]) > MaxTime) : - MaxTime = float(s[6]) - if ((("warning generated." in line) or ("warnings generated" in line)) and Mode == 1) : - s = line.split() - Warnings = Warnings + int(s[0]) - if (("The # of functions analysed (as top level)" in line) and (Mode == 1)) : - s = line.split() - FunctionsAnalyzed = FunctionsAnalyzed + int(s[0]) - if (("The % of reachable basic blocks" in line) and (Mode == 1)) : - s = line.split() - ReachableBlocks = ReachableBlocks + int(s[0]) - if (("The # of times we reached the max number of steps" in line) and (Mode == 1)) : - s = line.split() - ReachedMaxSteps = ReachedMaxSteps + int(s[0]) - if (("The maximum number of basic blocks in a function" in line) and (Mode == 1)) : - s = line.split() - if (MaxCFGSize < int(s[0])) : - MaxCFGSize = int(s[0]) - if (("The # of steps executed" in line) and (Mode == 1)) : - s = line.split() - NumSteps = NumSteps + int(s[0]) - if (("The # of times we inlined a call" in line) and (Mode == 1)) : - s = line.split() - NumInlinedCallSites = NumInlinedCallSites + int(s[0]) - if (("The # of times we split the path due to imprecise dynamic dispatch info" in line) and (Mode == 1)) : - s = line.split() - NumBifurcatedCallSites = NumBifurcatedCallSites + int(s[0]) - if ((") Total" in line) and (Mode == 1)) : - s = line.split() - TotalTime = TotalTime + float(s[6]) + if ("Analyzer Total Time" in line): + s = line.split() + Time = Time + float(s[6]) + Count = Count + 1 + if (float(s[6]) > MaxTime): + MaxTime = float(s[6]) + if ("warning generated." in line) or ("warnings generated" in line): + s = line.split() + Warnings = Warnings + int(s[0]) + if "The # of functions analysed (as top level)" in line: + s = line.split() + FunctionsAnalyzed = FunctionsAnalyzed + int(s[0]) + if "The % of reachable basic blocks" in line: + s = line.split() + ReachableBlocks = ReachableBlocks + int(s[0]) + if "The # of times we reached the max number of steps" in line: + s = line.split() + ReachedMaxSteps = ReachedMaxSteps + int(s[0]) + if "The maximum number of basic blocks in a function" in line: + s = line.split() + if MaxCFGSize < int(s[0]): + MaxCFGSize = int(s[0]) + if "The # of steps executed" in line: + s = line.split() + NumSteps = NumSteps + int(s[0]) + if "The # of times we inlined a call" in line: + s = line.split() + NumInlinedCallSites = NumInlinedCallSites + int(s[0]) + if "The # of times we split the path due \ + to imprecise dynamic dispatch info" in line: + s = line.split() + NumBifurcatedCallSites = NumBifurcatedCallSites + int(s[0]) + if ") Total" in line: + s = line.split() + TotalTime = TotalTime + float(s[6]) print "TU Count %d" % (Count) print "Time %f" % (Time) @@ -77,7 +72,8 @@ print "Reachable Blocks %d" % (ReachableBlocks) print "Reached Max Steps %d" % (ReachedMaxSteps) print "Number of Steps %d" % (NumSteps) - print "Number of Inlined calls %d (bifurcated %d)" % (NumInlinedCallSites, NumBifurcatedCallSites) + print "Number of Inlined calls %d (bifurcated %d)" % ( + NumInlinedCallSites, NumBifurcatedCallSites) print "MaxTime %f" % (MaxTime) print "TotalTime %f" % (TotalTime) print "Max CFG Size %d" % (MaxCFGSize) Index: utils/analyzer/ubiviz =================================================================== --- utils/analyzer/ubiviz +++ utils/analyzer/ubiviz @@ -5,69 +5,72 @@ # This file is distributed under the University of Illinois Open Source # License. See LICENSE.TXT for details. # -##===----------------------------------------------------------------------===## +##===--------------------------------------------------------------------===## # # This script reads visualization data emitted by the static analyzer for # display in Ubigraph. # -##===----------------------------------------------------------------------===## +##===--------------------------------------------------------------------===## import xmlrpclib import sys + def Error(message): print >> sys.stderr, 'ubiviz: ' + message sys.exit(1) + def StreamData(filename): - file = open(filename) - for ln in file: - yield eval(ln) - file.close() + file = open(filename) + for ln in file: + yield eval(ln) + file.close() + def Display(G, data): - action = data[0] - if action == 'vertex': - vertex = data[1] - G.new_vertex_w_id(vertex) - for attribute in data[2:]: - G.set_vertex_attribute(vertex, attribute[0], attribute[1]) - elif action == 'edge': - src = data[1] - dst = data[2] - edge = G.new_edge(src,dst) - for attribute in data[3:]: - G.set_edge_attribute(edge, attribute[0], attribute[1]) - elif action == "vertex_style": - style_id = data[1] - parent_id = data[2] - G.new_vertex_style_w_id(style_id, parent_id) - for attribute in data[3:]: - G.set_vertex_style_attribute(style_id, attribute[0], attribute[1]) - elif action == "vertex_style_attribute": - style_id = data[1] - for attribute in data[2:]: - G.set_vertex_style_attribute(style_id, attribute[0], attribute[1]) - elif action == "change_vertex_style": - vertex_id = data[1] - style_id = data[2] - G.change_vertex_style(vertex_id,style_id) + action = data[0] + if action == 'vertex': + vertex = data[1] + G.new_vertex_w_id(vertex) + for attribute in data[2:]: + G.set_vertex_attribute(vertex, attribute[0], attribute[1]) + elif action == 'edge': + src = data[1] + dst = data[2] + edge = G.new_edge(src, dst) + for attribute in data[3:]: + G.set_edge_attribute(edge, attribute[0], attribute[1]) + elif action == "vertex_style": + style_id = data[1] + parent_id = data[2] + G.new_vertex_style_w_id(style_id, parent_id) + for attribute in data[3:]: + G.set_vertex_style_attribute(style_id, attribute[0], attribute[1]) + elif action == "vertex_style_attribute": + style_id = data[1] + for attribute in data[2:]: + G.set_vertex_style_attribute(style_id, attribute[0], attribute[1]) + elif action == "change_vertex_style": + vertex_id = data[1] + style_id = data[2] + G.change_vertex_style(vertex_id, style_id) + def main(args): - if len(args) == 0: - Error('no input files') + if len(args) == 0: + Error('no input files') - server = xmlrpclib.Server('http://127.0.0.1:20738/RPC2') - G = server.ubigraph + server = xmlrpclib.Server('http://127.0.0.1:20738/RPC2') + G = server.ubigraph - for arg in args: - G.clear() - for x in StreamData(arg): - Display(G,x) + for arg in args: + G.clear() + for x in StreamData(arg): + Display(G, x) - sys.exit(0) + sys.exit(0) if __name__ == '__main__': main(sys.argv[1:]) -