blob: c735996c3b25ab40913e16642f2d64fd45d72b3e [file] [log] [blame]
Anna Zaksf0c41162011-10-06 23:26:27 +00001#!/usr/bin/env python
2
3"""
4Static Analyzer qualification infrastructure.
5
6The goal is to test the analyzer against different projects, check for failures,
7compare results, and measure performance.
8
Ted Kremenek3a0678e2015-09-08 03:50:52 +00009Repository Directory will contain sources of the projects as well as the
10information on how to build them and the expected output.
Anna Zaksf0c41162011-10-06 23:26:27 +000011Repository Directory structure:
12 - ProjectMap file
13 - Historical Performance Data
14 - Project Dir1
15 - ReferenceOutput
16 - Project Dir2
17 - ReferenceOutput
18 ..
Gabor Horvathc3177f22015-07-08 18:39:31 +000019Note that the build tree must be inside the project dir.
Anna Zaksf0c41162011-10-06 23:26:27 +000020
21To test the build of the analyzer one would:
Ted Kremenek3a0678e2015-09-08 03:50:52 +000022 - Copy over a copy of the Repository Directory. (TODO: Prefer to ensure that
Anna Zaksf0c41162011-10-06 23:26:27 +000023 the build directory does not pollute the repository to min network traffic).
24 - Build all projects, until error. Produce logs to report errors.
Ted Kremenek3a0678e2015-09-08 03:50:52 +000025 - Compare results.
Anna Zaksf0c41162011-10-06 23:26:27 +000026
Ted Kremenek3a0678e2015-09-08 03:50:52 +000027The files which should be kept around for failure investigations:
Anna Zaksf0c41162011-10-06 23:26:27 +000028 RepositoryCopy/Project DirI/ScanBuildResults
Ted Kremenek3a0678e2015-09-08 03:50:52 +000029 RepositoryCopy/Project DirI/run_static_analyzer.log
30
31Assumptions (TODO: shouldn't need to assume these.):
Anna Zaksf0c41162011-10-06 23:26:27 +000032 The script is being run from the Repository Directory.
Anna Zaks42a44632011-11-02 20:46:50 +000033 The compiler for scan-build and scan-build are in the PATH.
Anna Zaksf0c41162011-10-06 23:26:27 +000034 export PATH=/Users/zaks/workspace/c2llvm/build/Release+Asserts/bin:$PATH
35
36For more logging, set the env variables:
37 zaks:TI zaks$ export CCC_ANALYZER_LOG=1
38 zaks:TI zaks$ export CCC_ANALYZER_VERBOSE=1
Ted Kremenek3a0678e2015-09-08 03:50:52 +000039
Gabor Horvathda32a862015-08-20 22:59:49 +000040The list of checkers tested are hardcoded in the Checkers variable.
41For testing additional checkers, use the SA_ADDITIONAL_CHECKERS environment
42variable. It should contain a comma separated list.
Anna Zaksf0c41162011-10-06 23:26:27 +000043"""
44import CmpRuns
45
46import os
47import csv
48import sys
49import glob
Ted Kremenekf9a539d2012-08-28 20:40:04 +000050import math
Anna Zaksf0c41162011-10-06 23:26:27 +000051import shutil
52import time
53import plistlib
Gabor Horvath93fde942015-06-30 15:31:17 +000054import argparse
Devin Coughlinbace0322015-09-14 21:22:24 +000055from subprocess import check_call, check_output, CalledProcessError
George Karpenkov3abfc3b2017-09-22 01:41:16 +000056import multiprocessing
Anna Zaksf0c41162011-10-06 23:26:27 +000057
Ted Kremenek42c14422012-08-28 20:40:02 +000058#------------------------------------------------------------------------------
59# Helper functions.
60#------------------------------------------------------------------------------
Anna Zaksf0c41162011-10-06 23:26:27 +000061
Ted Kremenekf9a539d2012-08-28 20:40:04 +000062
Ted Kremenek6cb20802012-08-28 20:20:52 +000063def which(command, paths = None):
64 """which(command, [paths]) - Look up the given command in the paths string
65 (or the PATH environment variable, if unspecified)."""
66
67 if paths is None:
68 paths = os.environ.get('PATH','')
69
70 # Check for absolute match first.
71 if os.path.exists(command):
72 return command
73
74 # Would be nice if Python had a lib function for this.
75 if not paths:
76 paths = os.defpath
77
78 # Get suffixes to search.
79 # On Cygwin, 'PATHEXT' may exist but it should not be used.
80 if os.pathsep == ';':
81 pathext = os.environ.get('PATHEXT', '').split(';')
82 else:
83 pathext = ['']
84
85 # Search the paths...
86 for path in paths.split(os.pathsep):
87 for ext in pathext:
88 p = os.path.join(path, command + ext)
89 if os.path.exists(p):
90 return p
91
92 return None
93
Anna Zaksde1f7f8b2012-01-10 18:10:25 +000094# Make sure we flush the output after every print statement.
95class flushfile(object):
96 def __init__(self, f):
97 self.f = f
98 def write(self, x):
99 self.f.write(x)
100 self.f.flush()
101
102sys.stdout = flushfile(sys.stdout)
103
Anna Zaksf0c41162011-10-06 23:26:27 +0000104def getProjectMapPath():
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000105 ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
Anna Zaksf0c41162011-10-06 23:26:27 +0000106 ProjectMapFile)
107 if not os.path.exists(ProjectMapPath):
108 print "Error: Cannot find the Project Map file " + ProjectMapPath +\
109 "\nRunning script for the wrong directory?"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000110 sys.exit(-1)
111 return ProjectMapPath
Anna Zaksf0c41162011-10-06 23:26:27 +0000112
113def getProjectDir(ID):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000114 return os.path.join(os.path.abspath(os.curdir), ID)
Anna Zaksf0c41162011-10-06 23:26:27 +0000115
Jordan Rose01ac5722012-06-01 16:24:38 +0000116def getSBOutputDirName(IsReferenceBuild) :
Anna Zaks4720a732011-11-05 05:20:48 +0000117 if IsReferenceBuild == True :
118 return SBOutputDirReferencePrefix + SBOutputDirName
119 else :
120 return SBOutputDirName
121
Ted Kremenek42c14422012-08-28 20:40:02 +0000122#------------------------------------------------------------------------------
123# Configuration setup.
124#------------------------------------------------------------------------------
125
126# Find Clang for static analysis.
George Karpenkovbe6c3292017-09-21 22:12:49 +0000127if 'CC' in os.environ:
128 Clang = os.environ['CC']
129else:
130 Clang = which("clang", os.environ['PATH'])
Ted Kremenek42c14422012-08-28 20:40:02 +0000131if not Clang:
132 print "Error: cannot find 'clang' in PATH"
133 sys.exit(-1)
134
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000135# Number of jobs.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000136Jobs = int(math.ceil(multiprocessing.cpu_count() * 0.75))
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000137
Ted Kremenek42c14422012-08-28 20:40:02 +0000138# Project map stores info about all the "registered" projects.
139ProjectMapFile = "projectMap.csv"
140
141# Names of the project specific scripts.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000142# The script that downloads the project.
143DownloadScript = "download_project.sh"
Ted Kremenek42c14422012-08-28 20:40:02 +0000144# The script that needs to be executed before the build can start.
145CleanupScript = "cleanup_run_static_analyzer.sh"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000146# This is a file containing commands for scan-build.
Ted Kremenek42c14422012-08-28 20:40:02 +0000147BuildScript = "run_static_analyzer.cmd"
148
149# The log file name.
150LogFolderName = "Logs"
151BuildLogName = "run_static_analyzer.log"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000152# Summary file - contains the summary of the failures. Ex: This info can be be
Ted Kremenek42c14422012-08-28 20:40:02 +0000153# displayed when buildbot detects a build failure.
154NumOfFailuresInSummary = 10
155FailuresSummaryFileName = "failures.txt"
156# Summary of the result diffs.
157DiffsSummaryFileName = "diffs.txt"
158
159# The scan-build result directory.
160SBOutputDirName = "ScanBuildResults"
161SBOutputDirReferencePrefix = "Ref"
162
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000163# The name of the directory storing the cached project source. If this directory
164# does not exist, the download script will be executed. That script should
165# create the "CachedSource" directory and download the project source into it.
166CachedSourceDirName = "CachedSource"
167
168# The name of the directory containing the source code that will be analyzed.
169# Each time a project is analyzed, a fresh copy of its CachedSource directory
170# will be copied to the PatchedSource directory and then the local patches
171# in PatchfileName will be applied (if PatchfileName exists).
172PatchedSourceDirName = "PatchedSource"
173
174# The name of the patchfile specifying any changes that should be applied
175# to the CachedSource before analyzing.
176PatchfileName = "changes_for_analyzer.patch"
177
Ted Kremenek42c14422012-08-28 20:40:02 +0000178# The list of checkers used during analyzes.
Alp Tokerd4733632013-12-05 04:47:09 +0000179# Currently, consists of all the non-experimental checkers, plus a few alpha
Jordan Rose10ad0812013-04-05 17:55:07 +0000180# checkers we don't want to regress on.
George Karpenkovaf76b4a2017-09-30 00:05:24 +0000181Checkers=",".join([
182 "alpha.unix.SimpleStream",
183 "alpha.security.taint",
184 "cplusplus.NewDeleteLeaks",
185 "core",
186 "cplusplus",
187 "deadcode",
188 "security",
189 "unix",
190 "osx",
191 "nullability"
192])
Ted Kremenek42c14422012-08-28 20:40:02 +0000193
194Verbose = 1
195
196#------------------------------------------------------------------------------
197# Test harness logic.
198#------------------------------------------------------------------------------
199
Anna Zaksf0c41162011-10-06 23:26:27 +0000200# Run pre-processing script if any.
Anna Zaks42a44632011-11-02 20:46:50 +0000201def runCleanupScript(Dir, PBuildLogFile):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000202 Cwd = os.path.join(Dir, PatchedSourceDirName)
Anna Zaks42a44632011-11-02 20:46:50 +0000203 ScriptPath = os.path.join(Dir, CleanupScript)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000204 runScript(ScriptPath, PBuildLogFile, Cwd)
205
206# Run the script to download the project, if it exists.
207def runDownloadScript(Dir, PBuildLogFile):
208 ScriptPath = os.path.join(Dir, DownloadScript)
209 runScript(ScriptPath, PBuildLogFile, Dir)
210
211# Run the provided script if it exists.
212def runScript(ScriptPath, PBuildLogFile, Cwd):
Anna Zaksf0c41162011-10-06 23:26:27 +0000213 if os.path.exists(ScriptPath):
214 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000215 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000216 print " Executing: %s" % (ScriptPath,)
Devin Coughlinab95cd22016-01-22 07:08:06 +0000217 check_call("chmod +x '%s'" % ScriptPath, cwd = Cwd,
Anna Zaksf0c41162011-10-06 23:26:27 +0000218 stderr=PBuildLogFile,
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000219 stdout=PBuildLogFile,
220 shell=True)
Devin Coughlinab95cd22016-01-22 07:08:06 +0000221 check_call("'%s'" % ScriptPath, cwd = Cwd, stderr=PBuildLogFile,
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000222 stdout=PBuildLogFile,
Anna Zaksf0c41162011-10-06 23:26:27 +0000223 shell=True)
224 except:
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000225 print "Error: Running %s failed. See %s for details." % (ScriptPath,
226 PBuildLogFile.name)
Anna Zaksf0c41162011-10-06 23:26:27 +0000227 sys.exit(-1)
228
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000229# Download the project and apply the local patchfile if it exists.
230def downloadAndPatch(Dir, PBuildLogFile):
231 CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName)
232
233 # If the we don't already have the cached source, run the project's
234 # download script to download it.
235 if not os.path.exists(CachedSourceDirPath):
236 runDownloadScript(Dir, PBuildLogFile)
237 if not os.path.exists(CachedSourceDirPath):
238 print "Error: '%s' not found after download." % (CachedSourceDirPath)
239 exit(-1)
240
241 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
242
243 # Remove potentially stale patched source.
244 if os.path.exists(PatchedSourceDirPath):
245 shutil.rmtree(PatchedSourceDirPath)
246
247 # Copy the cached source and apply any patches to the copy.
248 shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True)
249 applyPatch(Dir, PBuildLogFile)
250
251def applyPatch(Dir, PBuildLogFile):
252 PatchfilePath = os.path.join(Dir, PatchfileName)
253 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
254 if not os.path.exists(PatchfilePath):
255 print " No local patches."
256 return
257
258 print " Applying patch."
259 try:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000260 check_call("patch -p1 < '%s'" % (PatchfilePath),
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000261 cwd = PatchedSourceDirPath,
262 stderr=PBuildLogFile,
263 stdout=PBuildLogFile,
264 shell=True)
265 except:
266 print "Error: Patch failed. See %s for details." % (PBuildLogFile.name)
267 sys.exit(-1)
268
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000269# Build the project with scan-build by reading in the commands and
Anna Zaksf0c41162011-10-06 23:26:27 +0000270# prefixing them with the scan-build options.
271def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
272 BuildScriptPath = os.path.join(Dir, BuildScript)
273 if not os.path.exists(BuildScriptPath):
274 print "Error: build script is not defined: %s" % BuildScriptPath
Ted Kremenek6cb20802012-08-28 20:20:52 +0000275 sys.exit(-1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000276
277 AllCheckers = Checkers
278 if os.environ.has_key('SA_ADDITIONAL_CHECKERS'):
279 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
280
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000281 # Run scan-build from within the patched source directory.
282 SBCwd = os.path.join(Dir, PatchedSourceDirName)
283
Devin Coughlin86f61a92016-01-22 18:45:22 +0000284 SBOptions = "--use-analyzer '%s' " % Clang
285 SBOptions += "-plist-html -o '%s' " % SBOutputDir
Devin Coughlinf3695c82015-09-16 01:52:32 +0000286 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000287 SBOptions += "--keep-empty "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000288 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000289 # directory.
290 SBOptions += "--override-compiler "
Anna Zaksf0c41162011-10-06 23:26:27 +0000291 try:
292 SBCommandFile = open(BuildScriptPath, "r")
293 SBPrefix = "scan-build " + SBOptions + " "
294 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000295 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000296 if len(Command) == 0:
297 continue;
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000298 # If using 'make', auto imply a -jX argument
299 # to speed up analysis. xcodebuild will
300 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000301 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000302 "-j" not in Command:
Jordan Rose88329242012-11-16 17:41:21 +0000303 Command += " -j%d" % Jobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000304 SBCommand = SBPrefix + Command
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000305 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000306 print " Executing: %s" % (SBCommand,)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000307 check_call(SBCommand, cwd = SBCwd, stderr=PBuildLogFile,
308 stdout=PBuildLogFile,
309 shell=True)
Anna Zaksf0c41162011-10-06 23:26:27 +0000310 except:
311 print "Error: scan-build failed. See ",PBuildLogFile.name,\
312 " for details."
Anna Zaks4720a732011-11-05 05:20:48 +0000313 raise
Anna Zaksf0c41162011-10-06 23:26:27 +0000314
Anna Zaks4720a732011-11-05 05:20:48 +0000315def hasNoExtension(FileName):
316 (Root, Ext) = os.path.splitext(FileName)
317 if ((Ext == "")) :
318 return True
319 return False
320
321def isValidSingleInputFile(FileName):
322 (Root, Ext) = os.path.splitext(FileName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000323 if ((Ext == ".i") | (Ext == ".ii") |
324 (Ext == ".c") | (Ext == ".cpp") |
Anna Zaks4720a732011-11-05 05:20:48 +0000325 (Ext == ".m") | (Ext == "")) :
326 return True
327 return False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000328
Devin Coughlinbace0322015-09-14 21:22:24 +0000329# Get the path to the SDK for the given SDK name. Returns None if
330# the path cannot be determined.
331def getSDKPath(SDKName):
332 if which("xcrun") is None:
333 return None
334
335 Cmd = "xcrun --sdk " + SDKName + " --show-sdk-path"
336 return check_output(Cmd, shell=True).rstrip()
337
Anna Zaks4720a732011-11-05 05:20:48 +0000338# Run analysis on a set of preprocessed files.
Anna Zaksa2f970b2012-09-06 23:30:27 +0000339def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
Anna Zaks4720a732011-11-05 05:20:48 +0000340 if os.path.exists(os.path.join(Dir, BuildScript)):
341 print "Error: The preprocessed files project should not contain %s" % \
342 BuildScript
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000343 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000344
Devin Coughlinbace0322015-09-14 21:22:24 +0000345 CmdPrefix = Clang + " -cc1 "
346
347 # For now, we assume the preprocessed files should be analyzed
348 # with the OS X SDK.
349 SDKPath = getSDKPath("macosx")
350 if SDKPath is not None:
351 CmdPrefix += "-isysroot " + SDKPath + " "
352
353 CmdPrefix += "-analyze -analyzer-output=plist -w "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000354 CmdPrefix += "-analyzer-checker=" + Checkers +" -fcxx-exceptions -fblocks "
355
Anna Zaksa2f970b2012-09-06 23:30:27 +0000356 if (Mode == 2) :
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000357 CmdPrefix += "-std=c++11 "
358
Anna Zaks4720a732011-11-05 05:20:48 +0000359 PlistPath = os.path.join(Dir, SBOutputDir, "date")
360 FailPath = os.path.join(PlistPath, "failures");
361 os.makedirs(FailPath);
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000362
Anna Zaks4720a732011-11-05 05:20:48 +0000363 for FullFileName in glob.glob(Dir + "/*"):
364 FileName = os.path.basename(FullFileName)
365 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000366
Anna Zaks4720a732011-11-05 05:20:48 +0000367 # Only run the analyzes on supported files.
368 if (hasNoExtension(FileName)):
369 continue
370 if (isValidSingleInputFile(FileName) == False):
371 print "Error: Invalid single input file %s." % (FullFileName,)
372 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000373
Anna Zaks4720a732011-11-05 05:20:48 +0000374 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000375 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
376 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000377 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
378 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000379 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000380 print " Executing: %s" % (Command,)
381 check_call(Command, cwd = Dir, stderr=LogFile,
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000382 stdout=LogFile,
Anna Zaks4720a732011-11-05 05:20:48 +0000383 shell=True)
384 except CalledProcessError, e:
385 print "Error: Analyzes of %s failed. See %s for details." \
386 "Error code %d." % \
387 (FullFileName, LogFile.name, e.returncode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000388 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000389 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000390 LogFile.close()
391
Anna Zaks4720a732011-11-05 05:20:48 +0000392 # If command did not fail, erase the log file.
393 if Failed == False:
394 os.remove(LogFile.name);
395
Devin Coughlin9ea80332016-01-23 01:09:07 +0000396def getBuildLogPath(SBOutputDir):
397 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
398
399def removeLogFile(SBOutputDir):
400 BuildLogPath = getBuildLogPath(SBOutputDir)
401 # Clean up the log file.
402 if (os.path.exists(BuildLogPath)) :
403 RmCommand = "rm '%s'" % BuildLogPath
404 if Verbose == 1:
405 print " Executing: %s" % (RmCommand,)
406 check_call(RmCommand, shell=True)
407
Anna Zaksa2f970b2012-09-06 23:30:27 +0000408def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000409 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000410
Devin Coughlin9ea80332016-01-23 01:09:07 +0000411 BuildLogPath = getBuildLogPath(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000412 print "Log file: %s" % (BuildLogPath,)
Anna Zaks4720a732011-11-05 05:20:48 +0000413 print "Output directory: %s" %(SBOutputDir, )
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000414
Devin Coughlin9ea80332016-01-23 01:09:07 +0000415 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000416
Anna Zaks4720a732011-11-05 05:20:48 +0000417 # Clean up scan build results.
418 if (os.path.exists(SBOutputDir)) :
Devin Coughlinab95cd22016-01-22 07:08:06 +0000419 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000420 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000421 print " Executing: %s" % (RmCommand,)
422 check_call(RmCommand, shell=True)
423 assert(not os.path.exists(SBOutputDir))
424 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000425
Anna Zaks4720a732011-11-05 05:20:48 +0000426 # Build and analyze the project.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000427 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000428 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000429 downloadAndPatch(Dir, PBuildLogFile)
430 runCleanupScript(Dir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000431 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
432 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000433 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000434
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000435 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000436 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000437 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000438
Anna Zaksf0c41162011-10-06 23:26:27 +0000439 print "Build complete (time: %.2f). See the log for more details: %s" % \
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000440 ((time.time()-TBegin), BuildLogPath)
441
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000442def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
443 """
444 Make the absolute paths relative in the reference results.
445 """
446 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
447 for F in Filenames:
448 if (not F.endswith('plist')):
449 continue
450 Plist = os.path.join(DirPath, F)
451 Data = plistlib.readPlist(Plist)
452 PathPrefix = Dir
453 if (ProjectBuildMode == 1):
454 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
455 Paths = [SourceFile[len(PathPrefix)+1:]\
456 if SourceFile.startswith(PathPrefix)\
457 else SourceFile for SourceFile in Data['files']]
458 Data['files'] = Paths
459 plistlib.writePlist(Data, Plist)
460
Anna Zaksf0c41162011-10-06 23:26:27 +0000461# A plist file is created for each call to the analyzer(each source file).
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000462# We are only interested on the once that have bug reports, so delete the rest.
Anna Zaksf0c41162011-10-06 23:26:27 +0000463def CleanUpEmptyPlists(SBOutputDir):
464 for F in glob.glob(SBOutputDir + "/*/*.plist"):
465 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000466
Anna Zaksf0c41162011-10-06 23:26:27 +0000467 Data = plistlib.readPlist(P)
468 # Delete empty reports.
469 if not Data['files']:
470 os.remove(P)
471 continue
472
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000473# Given the scan-build output directory, checks if the build failed
474# (by searching for the failures directories). If there are failures, it
475# creates a summary file in the output directory.
Anna Zaksf0c41162011-10-06 23:26:27 +0000476def checkBuild(SBOutputDir):
477 # Check if there are failures.
478 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
479 TotalFailed = len(Failures);
480 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000481 CleanUpEmptyPlists(SBOutputDir)
482 Plists = glob.glob(SBOutputDir + "/*/*.plist")
Alp Tokerd4733632013-12-05 04:47:09 +0000483 print "Number of bug reports (non-empty plist files) produced: %d" %\
Jordan Rose9858b122012-08-31 00:36:30 +0000484 len(Plists)
Anna Zaksf0c41162011-10-06 23:26:27 +0000485 return;
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000486
Anna Zaksf0c41162011-10-06 23:26:27 +0000487 # Create summary file to display when the build fails.
Anna Zaks4720a732011-11-05 05:20:48 +0000488 SummaryPath = os.path.join(SBOutputDir, LogFolderName, FailuresSummaryFileName)
Anna Zaksf0c41162011-10-06 23:26:27 +0000489 if (Verbose > 0):
Anna Zaks4720a732011-11-05 05:20:48 +0000490 print " Creating the failures summary file %s" % (SummaryPath,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000491
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000492 with open(SummaryPath, "w+") as SummaryLog:
Anna Zaksf0c41162011-10-06 23:26:27 +0000493 SummaryLog.write("Total of %d failures discovered.\n" % (TotalFailed,))
494 if TotalFailed > NumOfFailuresInSummary:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000495 SummaryLog.write("See the first %d below.\n"
Anna Zaksf0c41162011-10-06 23:26:27 +0000496 % (NumOfFailuresInSummary,))
497 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000498
Anna Zaksf0c41162011-10-06 23:26:27 +0000499 Idx = 0
Jordan Rosedc191a12012-06-01 16:24:43 +0000500 for FailLogPathI in Failures:
Anna Zaksf0c41162011-10-06 23:26:27 +0000501 if Idx >= NumOfFailuresInSummary:
502 break;
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000503 Idx += 1
Anna Zaksf0c41162011-10-06 23:26:27 +0000504 SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,));
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000505 with open(FailLogPathI, "r") as FailLogI:
Anna Zaksf0c41162011-10-06 23:26:27 +0000506 shutil.copyfileobj(FailLogI, SummaryLog);
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000507
Anna Zaks5acd9602012-01-04 23:53:50 +0000508 print "Error: analysis failed. See ", SummaryPath
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000509 sys.exit(-1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000510
511# Auxiliary object to discard stdout.
512class Discarder(object):
513 def write(self, text):
514 pass # do nothing
515
516# Compare the warnings produced by scan-build.
Gabor Horvath93fde942015-06-30 15:31:17 +0000517# Strictness defines the success criteria for the test:
518# 0 - success if there are no crashes or analyzer failure.
519# 1 - success if there are no difference in the number of reported bugs.
520# 2 - success if all the bug reports are identical.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000521def runCmpResults(Dir, Strictness = 0):
522 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000523
524 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
525 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000526
Anna Zaksf0c41162011-10-06 23:26:27 +0000527 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000528 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000529 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000530
Jordan Rosec7b992e2013-06-10 19:34:30 +0000531 # Log folders are also located in the results dir, so ignore them.
532 RefLogDir = os.path.join(RefDir, LogFolderName)
533 if RefLogDir in RefList:
534 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000535 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000536
Anna Zaksf0c41162011-10-06 23:26:27 +0000537 if len(RefList) == 0 or len(NewList) == 0:
538 return False
539 assert(len(RefList) == len(NewList))
540
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000541 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000542 # command (Ex: one for configure and one for make).
543 if (len(RefList) > 1):
544 # Assume that the corresponding folders have the same names.
545 RefList.sort()
546 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000547
Anna Zaksf0c41162011-10-06 23:26:27 +0000548 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000549 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000550 PairList = zip(RefList, NewList)
551 for P in PairList:
552 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000553 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000554
555 assert(RefDir != NewDir)
556 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000557 print " Comparing Results: %s %s" % (RefDir, NewDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000558
Anna Zaksf0c41162011-10-06 23:26:27 +0000559 DiffsPath = os.path.join(NewDir, DiffsSummaryFileName)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000560 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
561 Opts = CmpRuns.CmpOptions(DiffsPath, "", PatchedSourceDirPath)
Anna Zaksf0c41162011-10-06 23:26:27 +0000562 # Discard everything coming out of stdout (CmpRun produces a lot of them).
563 OLD_STDOUT = sys.stdout
564 sys.stdout = Discarder()
565 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000566 NumDiffs, ReportsInRef, ReportsInNew = \
567 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False)
Anna Zaksf0c41162011-10-06 23:26:27 +0000568 sys.stdout = OLD_STDOUT
Anna Zaks767d3562011-11-08 19:56:31 +0000569 if (NumDiffs > 0) :
570 print "Warning: %r differences in diagnostics. See %s" % \
571 (NumDiffs, DiffsPath,)
Gabor Horvath93fde942015-06-30 15:31:17 +0000572 if Strictness >= 2 and NumDiffs > 0:
573 print "Error: Diffs found in strict mode (2)."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000574 sys.exit(-1)
Gabor Horvath93fde942015-06-30 15:31:17 +0000575 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
576 print "Error: The number of results are different in strict mode (1)."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000577 sys.exit(-1)
578
579 print "Diagnostic comparison complete (time: %.2f)." % (time.time()-TBegin)
Anna Zaks767d3562011-11-08 19:56:31 +0000580 return (NumDiffs > 0)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000581
Devin Coughlin9ea80332016-01-23 01:09:07 +0000582def cleanupReferenceResults(SBOutputDir):
583 # Delete html, css, and js files from reference results. These can
584 # include multiple copies of the benchmark source and so get very large.
585 Extensions = ["html", "css", "js"]
586 for E in Extensions:
587 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
588 P = os.path.join(SBOutputDir, F)
589 RmCommand = "rm '%s'" % P
590 check_call(RmCommand, shell=True)
591
592 # Remove the log file. It leaks absolute path names.
593 removeLogFile(SBOutputDir)
594
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000595def updateSVN(Mode, PMapFile):
596 """
597 svn delete or svn add (depending on `Mode`) all folders defined in the file
598 handler `PMapFile`.
599 Commit the result to SVN.
600 """
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000601 try:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000602 for I in iterateOverProjects(PMapFile):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000603 ProjName = I[0]
Jordan Rose01ac5722012-06-01 16:24:38 +0000604 Path = os.path.join(ProjName, getSBOutputDirName(True))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000605
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000606 if Mode == "delete":
Devin Coughlinab95cd22016-01-22 07:08:06 +0000607 Command = "svn delete '%s'" % (Path,)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000608 else:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000609 Command = "svn add '%s'" % (Path,)
Anna Zaksf0c41162011-10-06 23:26:27 +0000610
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000611 if Verbose == 1:
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000612 print " Executing: %s" % (Command,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000613 check_call(Command, shell=True)
614
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000615 if Mode == "delete":
616 CommitCommand = "svn commit -m \"[analyzer tests] Remove " \
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000617 "reference results.\""
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000618 else:
619 CommitCommand = "svn commit -m \"[analyzer tests] Add new " \
620 "reference results.\""
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000621 if Verbose == 1:
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000622 print " Executing: %s" % (CommitCommand,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000623 check_call(CommitCommand, shell=True)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000624 except:
625 print "Error: SVN update failed."
626 sys.exit(-1)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000627
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000628def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness = 0):
Anna Zaks4720a732011-11-05 05:20:48 +0000629 print " \n\n--- Building project %s" % (ID,)
630
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000631 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000632
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000633 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000634 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000635 print " Build directory: %s." % (Dir,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000636
Anna Zaksf0c41162011-10-06 23:26:27 +0000637 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000638 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000639 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000640
Anna Zaksa2f970b2012-09-06 23:30:27 +0000641 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000642
643 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000644
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000645 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000646 cleanupReferenceResults(SBOutputDir)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000647 else:
648 runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000649
Anna Zaksf0c41162011-10-06 23:26:27 +0000650 print "Completed tests for project %s (time: %.2f)." % \
651 (ID, (time.time()-TBegin))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000652
Devin Coughlin0dfc6f02016-09-19 01:36:40 +0000653def isCommentCSVLine(Entries):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000654 # Treat CSV lines starting with a '#' as a comment.
655 return len(Entries) > 0 and Entries[0].startswith("#")
Devin Coughlin0dfc6f02016-09-19 01:36:40 +0000656
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000657def projectFileHandler():
658 return open(getProjectMapPath(), "rb")
659
660def iterateOverProjects(PMapFile):
661 """
662 Iterate over all projects defined in the project file handler `PMapFile`
663 from the start.
664 """
665 PMapFile.seek(0)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000666 try:
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000667 for I in csv.reader(PMapFile):
Devin Coughlin0dfc6f02016-09-19 01:36:40 +0000668 if (isCommentCSVLine(I)):
669 continue
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000670 yield I
671 except:
672 print "Error occurred. Premature termination."
673 raise
674
675def validateProjectFile(PMapFile):
676 """
677 Validate project file.
678 """
679 for I in iterateOverProjects(PMapFile):
680 if (len(I) != 2) :
681 print "Error: Rows in the ProjectMapFile should have 2 entries."
682 raise Exception()
683 if (not ((I[1] == "0") | (I[1] == "1") | (I[1] == "2"))):
684 print "Error: Second entry in the ProjectMapFile should be 0" \
685 " (single file), 1 (project), or 2(single file c++11)."
686 raise Exception()
687
688def testAll(IsReferenceBuild = False, UpdateSVN = False, Strictness = 0):
689 with projectFileHandler() as PMapFile:
690 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000691
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000692 # When we are regenerating the reference results, we might need to
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000693 # update svn. Remove reference results from SVN.
694 if UpdateSVN == True:
Jordan Rose01ac5722012-06-01 16:24:38 +0000695 assert(IsReferenceBuild == True);
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000696 updateSVN("delete", PMapFile);
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000697
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000698 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000699 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
700 testProject(ProjName, int(ProjBuildMode), IsReferenceBuild, Strictness)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000701
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000702 # Re-add reference results to SVN.
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000703 if UpdateSVN == True:
704 updateSVN("add", PMapFile);
705
Anna Zaksf0c41162011-10-06 23:26:27 +0000706if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000707 # Parse command line arguments.
708 Parser = argparse.ArgumentParser(description='Test the Clang Static Analyzer.')
709 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
710 help='0 to fail on runtime errors, 1 to fail when the number\
711 of found bugs are different from the reference, 2 to \
712 fail on any difference from the reference. Default is 0.')
713 Parser.add_argument('-r', dest='regenerate', action='store_true', default=False,
714 help='Regenerate reference output.')
715 Parser.add_argument('-rs', dest='update_reference', action='store_true',
716 default=False, help='Regenerate reference output and update svn.')
717 Args = Parser.parse_args()
718
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000719 IsReference = False
720 UpdateSVN = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000721 Strictness = Args.strictness
722 if Args.regenerate:
723 IsReference = True
724 elif Args.update_reference:
725 IsReference = True
726 UpdateSVN = True
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000727
Gabor Horvath93fde942015-06-30 15:31:17 +0000728 testAll(IsReference, UpdateSVN, Strictness)