diff options
Diffstat (limited to 'utils/analyzer')
| -rwxr-xr-x | utils/analyzer/CmpRuns.py | 92 | ||||
| -rw-r--r-- | utils/analyzer/SATestAdd.py | 18 | ||||
| -rw-r--r-- | utils/analyzer/SATestBuild.py | 153 | ||||
| -rw-r--r-- | utils/analyzer/SumTimerInfo.py | 17 | ||||
| -rwxr-xr-x | utils/analyzer/update_plist_test.pl | 51 |
5 files changed, 250 insertions, 81 deletions
diff --git a/utils/analyzer/CmpRuns.py b/utils/analyzer/CmpRuns.py index c8f05cbcf474..3ca9b2bbe7eb 100755 --- a/utils/analyzer/CmpRuns.py +++ b/utils/analyzer/CmpRuns.py @@ -17,11 +17,8 @@ Usage: # Load the results of both runs, to obtain lists of the corresponding # AnalysisDiagnostic objects. # - # root - the name of the root directory, which will be disregarded when - # determining the source file name - # - resultsA = loadResults(dirA, opts, root, deleteEmpty) - resultsB = loadResults(dirB, opts, root, deleteEmpty) + resultsA = loadResultsFromSingleRun(singleRunInfoA, deleteEmpty) + resultsB = loadResultsFromSingleRun(singleRunInfoB, deleteEmpty) # Generate a relation from diagnostics in run A to diagnostics in run B # to obtain a list of triples (a, b, confidence). @@ -31,8 +28,18 @@ Usage: import os import plistlib +import CmpRuns + +# Information about analysis run: +# path - the analysis output directory +# root - the name of the root directory, which will be disregarded when +# determining the source file name +class SingleRunInfo: + def __init__(self, path, root="", verboseLog=None): + self.path = path + self.root = root + self.verboseLog = verboseLog -# class AnalysisDiagnostic: def __init__(self, data, report, htmlReport): self._data = data @@ -41,7 +48,11 @@ class AnalysisDiagnostic: self._htmlReport = htmlReport def getFileName(self): - return self._report.run.getSourceName(self._report.files[self._loc['file']]) + root = self._report.run.root + fileName = self._report.files[self._loc['file']] + if fileName.startswith(root) : + return fileName[len(root):] + return fileName def getLine(self): return self._loc['line'] @@ -56,12 +67,12 @@ class AnalysisDiagnostic: return self._data['description'] def getIssueIdentifier(self) : - id = '' + id = self.getFileName() + "+" if 'issue_context' in self._data : - id += self._data['issue_context'] + ":" + id += self._data['issue_context'] + "+" if 'issue_hash' in self._data : - id += str(self._data['issue_hash']) + ":" - return id + ":" + self.getFileName() + id += str(self._data['issue_hash']) + return id def getReport(self): if self._htmlReport is None: @@ -72,6 +83,11 @@ class AnalysisDiagnostic: return '%s:%d:%d, %s: %s' % (self.getFileName(), self.getLine(), self.getColumn(), self.getCategory(), self.getDescription()) + + # Note, the data format is not an API and may change from one analyzer + # version to another. + def getRawData(self): + return self._data class multidict: def __init__(self, elts=()): @@ -96,8 +112,6 @@ class multidict: return len(self.data) def get(self, key, default=None): return self.data.get(key, default) - -# class CmpOptions: def __init__(self, verboseLog=None, rootA="", rootB=""): @@ -106,29 +120,36 @@ class CmpOptions: self.verboseLog = verboseLog class AnalysisReport: - def __init__(self, run, files): + def __init__(self, run, files, clang_vers): self.run = run + self.clang_version = clang_vers self.files = files + self.diagnostics = [] class AnalysisRun: - def __init__(self, path, root, opts): - self.path = path - self.root = root + def __init__(self, info): + self.path = info.path + self.root = info.root + self.info = info self.reports = [] + # Cumulative list of all diagnostics from all the reports. self.diagnostics = [] - self.opts = opts - def getSourceName(self, path): - if path.startswith(self.root): - return path[len(self.root):] - return path +# Backward compatibility API. def loadResults(path, opts, root = "", deleteEmpty=True): - run = AnalysisRun(path, root, opts) + 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): + path = info.path + run = AnalysisRun(info) for f in os.listdir(path): - if (not f.startswith('report') or - not f.endswith('plist')): + if (not f.endswith('plist')): continue p = os.path.join(path, f) @@ -146,20 +167,23 @@ def loadResults(path, opts, root = "", deleteEmpty=True): for d in data['diagnostics']: # FIXME: Why is this named files, when does it have multiple # files? - # TODO: Add the assert back in after we fix the - # plist-html output. - # assert len(d['HTMLDiagnostics_files']) == 1 + assert len(d['HTMLDiagnostics_files']) == 1 htmlFiles.append(d.pop('HTMLDiagnostics_files')[0]) else: htmlFiles = [None] * len(data['diagnostics']) - - report = AnalysisReport(run, data.pop('files')) + + clang_version = '' + if 'clang_version' in data: + clang_version = data.pop('clang_version') + + report = AnalysisReport(run, data.pop('files'), clang_version) diagnostics = [AnalysisDiagnostic(d, report, h) for d,h in zip(data.pop('diagnostics'), htmlFiles)] assert not data - + + report.diagnostics.extend(diagnostics) run.reports.append(report) run.diagnostics.extend(diagnostics) @@ -193,12 +217,12 @@ def compareResults(A, B): b = eltsB.pop() if (a.getIssueIdentifier() == b.getIssueIdentifier()) : res.append((a, b, 0)) - elif a._data > b._data: - neqA.append(a) + elif a.getIssueIdentifier() > b.getIssueIdentifier(): eltsB.append(b) + neqA.append(a) else: - neqB.append(b) eltsA.append(a) + neqB.append(b) neqA.extend(eltsA) neqB.extend(eltsB) diff --git a/utils/analyzer/SATestAdd.py b/utils/analyzer/SATestAdd.py index 2d32533f6bb0..64ff4ff65683 100644 --- a/utils/analyzer/SATestAdd.py +++ b/utils/analyzer/SATestAdd.py @@ -33,7 +33,7 @@ def isExistingProject(PMapFile, projectID) : # Params: # Dir is the directory where the sources are. # ID is a short string used to identify a project. -def addNewProject(ID, IsScanBuild) : +def addNewProject(ID, BuildMode) : CurDir = os.path.abspath(os.curdir) Dir = SATestBuild.getProjectDir(ID) if not os.path.exists(Dir): @@ -41,7 +41,7 @@ def addNewProject(ID, IsScanBuild) : sys.exit(-1) # Build the project. - SATestBuild.testProject(ID, IsScanBuild, IsReferenceBuild=True, Dir=Dir) + SATestBuild.testProject(ID, BuildMode, IsReferenceBuild=True, Dir=Dir) # Add the project ID to the project map. ProjectMapPath = os.path.join(CurDir, SATestBuild.ProjectMapFile) @@ -57,7 +57,7 @@ def addNewProject(ID, IsScanBuild) : print >> sys.stdout, "Reference output has been regenerated." else: PMapWriter = csv.writer(PMapFile) - PMapWriter.writerow( (ID, int(IsScanBuild)) ); + PMapWriter.writerow( (ID, int(BuildMode)) ); print "The project map is updated: ", ProjectMapPath finally: PMapFile.close() @@ -69,12 +69,14 @@ if __name__ == '__main__': if len(sys.argv) < 2: print >> sys.stderr, 'Usage: ', sys.argv[0],\ 'project_ID <mode>' \ - 'mode - 0 for single file project; 1 for scan_build' + 'mode - 0 for single file project; ' \ + '1 for scan_build; ' \ + '2 for single file c++11 project' sys.exit(-1) - IsScanBuild = 1 + BuildMode = 1 if (len(sys.argv) >= 3): - IsScanBuild = int(sys.argv[2]) - assert((IsScanBuild == 0) | (IsScanBuild == 1)) + BuildMode = int(sys.argv[2]) + assert((BuildMode == 0) | (BuildMode == 1) | (BuildMode == 2)) - addNewProject(sys.argv[1], IsScanBuild) + addNewProject(sys.argv[1], BuildMode) diff --git a/utils/analyzer/SATestBuild.py b/utils/analyzer/SATestBuild.py index fd4bc8a8eeef..94123582225f 100644 --- a/utils/analyzer/SATestBuild.py +++ b/utils/analyzer/SATestBuild.py @@ -42,39 +42,66 @@ import os import csv import sys import glob +import math import shutil import time import plistlib from subprocess import check_call, CalledProcessError -# Project map stores info about all the "registered" projects. -ProjectMapFile = "projectMap.csv" +#------------------------------------------------------------------------------ +# Helper functions. +#------------------------------------------------------------------------------ -# Names of the project specific scripts. -# The script that needs to be executed before the build can start. -CleanupScript = "cleanup_run_static_analyzer.sh" -# This is a file containing commands for scan-build. -BuildScript = "run_static_analyzer.cmd" +def detectCPUs(): + """ + Detects the number of CPUs on a system. Cribbed from pp. + """ + # Linux, Unix and MacOS: + if hasattr(os, "sysconf"): + if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"): + # Linux & Unix: + ncpus = os.sysconf("SC_NPROCESSORS_ONLN") + if isinstance(ncpus, int) and ncpus > 0: + return ncpus + else: # OSX: + return int(capture(['sysctl', '-n', 'hw.ncpu'])) + # Windows: + if os.environ.has_key("NUMBER_OF_PROCESSORS"): + ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]) + if ncpus > 0: + return ncpus + return 1 # Default -# The log file name. -LogFolderName = "Logs" -BuildLogName = "run_static_analyzer.log" -# Summary file - contains the summary of the failures. Ex: This info can be be -# displayed when buildbot detects a build failure. -NumOfFailuresInSummary = 10 -FailuresSummaryFileName = "failures.txt" -# Summary of the result diffs. -DiffsSummaryFileName = "diffs.txt" +def which(command, paths = None): + """which(command, [paths]) - Look up the given command in the paths string + (or the PATH environment variable, if unspecified).""" -# The scan-build result directory. -SBOutputDirName = "ScanBuildResults" -SBOutputDirReferencePrefix = "Ref" + if paths is None: + paths = os.environ.get('PATH','') -# The list of checkers used during analyzes. -# Currently, consists of all the non experimental checkers. -Checkers="experimental.security.taint,core,deadcode,security,unix,osx" + # Check for absolute match first. + if os.path.exists(command): + return command -Verbose = 1 + # 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 = [''] + + # 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 # Make sure we flush the output after every print statement. class flushfile(object): @@ -104,6 +131,52 @@ def getSBOutputDirName(IsReferenceBuild) : else : return SBOutputDirName +#------------------------------------------------------------------------------ +# Configuration setup. +#------------------------------------------------------------------------------ + +# Find Clang for static analysis. +Clang = which("clang", os.environ['PATH']) +if not Clang: + print "Error: cannot find 'clang' in PATH" + sys.exit(-1) + +# Number of jobs. +Jobs = math.ceil(detectCPUs() * 0.75) + +# Project map stores info about all the "registered" projects. +ProjectMapFile = "projectMap.csv" + +# Names of the project specific scripts. +# The script that needs to be executed before the build can start. +CleanupScript = "cleanup_run_static_analyzer.sh" +# This is a file containing commands for scan-build. +BuildScript = "run_static_analyzer.cmd" + +# The log file name. +LogFolderName = "Logs" +BuildLogName = "run_static_analyzer.log" +# Summary file - contains the summary of the failures. Ex: This info can be be +# displayed when buildbot detects a build failure. +NumOfFailuresInSummary = 10 +FailuresSummaryFileName = "failures.txt" +# Summary of the result diffs. +DiffsSummaryFileName = "diffs.txt" + +# The scan-build result directory. +SBOutputDirName = "ScanBuildResults" +SBOutputDirReferencePrefix = "Ref" + +# The list of checkers used during analyzes. +# Currently, consists of all the non experimental checkers. +Checkers="alpha.unix.SimpleStream,alpha.security.taint,core,deadcode,security,unix,osx" + +Verbose = 1 + +#------------------------------------------------------------------------------ +# Test harness logic. +#------------------------------------------------------------------------------ + # Run pre-processing script if any. def runCleanupScript(Dir, PBuildLogFile): ScriptPath = os.path.join(Dir, CleanupScript) @@ -129,13 +202,19 @@ def runScanBuild(Dir, SBOutputDir, PBuildLogFile): BuildScriptPath = os.path.join(Dir, BuildScript) if not os.path.exists(BuildScriptPath): print "Error: build script is not defined: %s" % BuildScriptPath - sys.exit(-1) - SBOptions = "-plist-html -o " + SBOutputDir + " " + sys.exit(-1) + SBOptions = "--use-analyzer " + Clang + " " + SBOptions += "-plist-html -o " + SBOutputDir + " " SBOptions += "-enable-checker " + Checkers + " " try: SBCommandFile = open(BuildScriptPath, "r") SBPrefix = "scan-build " + SBOptions + " " for Command in SBCommandFile: + # If using 'make', auto imply a -jX argument + # to speed up analysis. xcodebuild will + # automatically use the maximum number of cores. + if Command.startswith("make "): + Command += "-j" + Jobs SBCommand = SBPrefix + Command if Verbose == 1: print " Executing: %s" % (SBCommand,) @@ -160,17 +239,20 @@ def isValidSingleInputFile(FileName): (Ext == ".m") | (Ext == "")) : return True return False - + # Run analysis on a set of preprocessed files. -def runAnalyzePreprocessed(Dir, SBOutputDir): +def runAnalyzePreprocessed(Dir, SBOutputDir, Mode): if os.path.exists(os.path.join(Dir, BuildScript)): print "Error: The preprocessed files project should not contain %s" % \ BuildScript raise Exception() - CmdPrefix = "clang -cc1 -analyze -analyzer-output=plist -w " + CmdPrefix = Clang + " -cc1 -analyze -analyzer-output=plist -w " CmdPrefix += "-analyzer-checker=" + Checkers +" -fcxx-exceptions -fblocks " + if (Mode == 2) : + CmdPrefix += "-std=c++11 " + PlistPath = os.path.join(Dir, SBOutputDir, "date") FailPath = os.path.join(PlistPath, "failures"); os.makedirs(FailPath); @@ -208,7 +290,7 @@ def runAnalyzePreprocessed(Dir, SBOutputDir): if Failed == False: os.remove(LogFile.name); -def buildProject(Dir, SBOutputDir, IsScanBuild, IsReferenceBuild): +def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild): TBegin = time.time() BuildLogPath = os.path.join(SBOutputDir, LogFolderName, BuildLogName) @@ -238,10 +320,10 @@ def buildProject(Dir, SBOutputDir, IsScanBuild, IsReferenceBuild): try: runCleanupScript(Dir, PBuildLogFile) - if IsScanBuild: + if (ProjectBuildMode == 1): runScanBuild(Dir, SBOutputDir, PBuildLogFile) else: - runAnalyzePreprocessed(Dir, SBOutputDir) + runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode) if IsReferenceBuild : runCleanupScript(Dir, PBuildLogFile) @@ -395,7 +477,7 @@ def updateSVN(Mode, ProjectsMap): print "Error: SVN update failed." sys.exit(-1) -def testProject(ID, IsScanBuild, IsReferenceBuild=False, Dir=None): +def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Dir=None): print " \n\n--- Building project %s" % (ID,) TBegin = time.time() @@ -409,7 +491,7 @@ def testProject(ID, IsScanBuild, IsReferenceBuild=False, Dir=None): RelOutputDir = getSBOutputDirName(IsReferenceBuild) SBOutputDir = os.path.join(Dir, RelOutputDir) - buildProject(Dir, SBOutputDir, IsScanBuild, IsReferenceBuild) + buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild) checkBuild(SBOutputDir) @@ -427,8 +509,9 @@ def testAll(IsReferenceBuild = False, UpdateSVN = False): if (len(I) != 2) : print "Error: Rows in the ProjectMapFile should have 3 entries." raise Exception() - if (not ((I[1] == "1") | (I[1] == "0"))): - print "Error: Second entry in the ProjectMapFile should be 0 or 1." + 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)." raise Exception() # When we are regenerating the reference results, we might need to diff --git a/utils/analyzer/SumTimerInfo.py b/utils/analyzer/SumTimerInfo.py index a6731bb8f243..4ef1ceb4cec5 100644 --- a/utils/analyzer/SumTimerInfo.py +++ b/utils/analyzer/SumTimerInfo.py @@ -28,6 +28,8 @@ if __name__ == '__main__': ReachableBlocks = 0 ReachedMaxSteps = 0 NumSteps = 0 + NumInlinedCallSites = 0 + NumBifurcatedCallSites = 0 MaxCFGSize = 0 Mode = 1 for line in f: @@ -39,25 +41,31 @@ if __name__ == '__main__': 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) : + 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)) : + 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)) : + 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)) : + 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]) @@ -69,6 +77,7 @@ if __name__ == '__main__': 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 "MaxTime %f" % (MaxTime) print "TotalTime %f" % (TotalTime) print "Max CFG Size %d" % (MaxCFGSize) diff --git a/utils/analyzer/update_plist_test.pl b/utils/analyzer/update_plist_test.pl new file mode 100755 index 000000000000..abb74a57c3c1 --- /dev/null +++ b/utils/analyzer/update_plist_test.pl @@ -0,0 +1,51 @@ +#!/usr/bin/perl -w +use strict; +require File::Temp; +use File::Temp (); + +die "update_plist_test <test file> <plist file>\n" if ($#ARGV < 1); +my $testFile = shift @ARGV; +die "error: cannot read file $testFile\n" if (! -r $testFile); +my $plistFile = shift @ARGV; +die "error: cannot read file $plistFile\n" if (! -r $plistFile); + +# Create a temp file for the new test. +my $fh = File::Temp->new(); +my $filename = $fh->filename; +$fh->unlink_on_destroy(1); + +# Copy the existing temp file, skipping the FileCheck comments. +open (IN, $testFile) or die "cannot open $testFile\n"; +while (<IN>) { + next if (/^\/\/ CHECK/); + print $fh $_; +} +close(IN); + +# Copy the plist data, and specially format it. +open (IN, $plistFile) or die "cannot open $plistFile\n"; +my $firstArray = 1; +my $first = 1; +while (<IN>) { + # Skip everything not indented. + next if (/^[^\s]/); + # Skip the first array entry, which is for files. + if ($firstArray) { + if (/<\/array>/) { $firstArray = 0; } + next; + } + # Format the CHECK lines. + if ($first) { + print $fh "// CHECK: "; + $first = 0; + } + else { + print $fh "// CHECK-NEXT: "; + } + print $fh $_; +} +close (IN); +close ($fh); + +`cp $filename $testFile`; +print "updated $testFile\n"; |
