blob: ed700c0c8eb9f4f42c89cc56b19eeeadebe0f763 [file] [log] [blame]
Anna Zaksf0c41162011-10-06 23:26:27 +00001#!/usr/bin/env python
2
3"""
4Static Analyzer qualification infrastructure.
5
George Karpenkova8076602017-10-02 17:59:12 +00006The goal is to test the analyzer against different projects,
7check for failures, compare results, and measure performance.
Anna Zaksf0c41162011-10-06 23:26:27 +00008
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
George Karpenkova8076602017-10-02 17:59:12 +000023 the build directory does not pollute the repository to min network
24 traffic).
Anna Zaksf0c41162011-10-06 23:26:27 +000025 - Build all projects, until error. Produce logs to report errors.
Ted Kremenek3a0678e2015-09-08 03:50:52 +000026 - Compare results.
Anna Zaksf0c41162011-10-06 23:26:27 +000027
Ted Kremenek3a0678e2015-09-08 03:50:52 +000028The files which should be kept around for failure investigations:
Anna Zaksf0c41162011-10-06 23:26:27 +000029 RepositoryCopy/Project DirI/ScanBuildResults
Ted Kremenek3a0678e2015-09-08 03:50:52 +000030 RepositoryCopy/Project DirI/run_static_analyzer.log
31
32Assumptions (TODO: shouldn't need to assume these.):
Anna Zaksf0c41162011-10-06 23:26:27 +000033 The script is being run from the Repository Directory.
Anna Zaks42a44632011-11-02 20:46:50 +000034 The compiler for scan-build and scan-build are in the PATH.
Anna Zaksf0c41162011-10-06 23:26:27 +000035 export PATH=/Users/zaks/workspace/c2llvm/build/Release+Asserts/bin:$PATH
36
37For more logging, set the env variables:
38 zaks:TI zaks$ export CCC_ANALYZER_LOG=1
39 zaks:TI zaks$ export CCC_ANALYZER_VERBOSE=1
Ted Kremenek3a0678e2015-09-08 03:50:52 +000040
Gabor Horvathda32a862015-08-20 22:59:49 +000041The list of checkers tested are hardcoded in the Checkers variable.
42For testing additional checkers, use the SA_ADDITIONAL_CHECKERS environment
43variable. It should contain a comma separated list.
Anna Zaksf0c41162011-10-06 23:26:27 +000044"""
45import CmpRuns
George Karpenkovbf92c442017-10-24 23:52:48 +000046import SATestUtils
Anna Zaksf0c41162011-10-06 23:26:27 +000047
48import os
49import csv
50import sys
51import glob
Ted Kremenekf9a539d2012-08-28 20:40:04 +000052import math
Anna Zaksf0c41162011-10-06 23:26:27 +000053import shutil
54import time
55import plistlib
Gabor Horvath93fde942015-06-30 15:31:17 +000056import argparse
George Karpenkovbf92c442017-10-24 23:52:48 +000057from subprocess import check_call, CalledProcessError
George Karpenkov3abfc3b2017-09-22 01:41:16 +000058import multiprocessing
Anna Zaksf0c41162011-10-06 23:26:27 +000059
Ted Kremenek42c14422012-08-28 20:40:02 +000060#------------------------------------------------------------------------------
61# Helper functions.
62#------------------------------------------------------------------------------
Anna Zaksf0c41162011-10-06 23:26:27 +000063
Ted Kremenekf9a539d2012-08-28 20:40:04 +000064
George Karpenkovbf92c442017-10-24 23:52:48 +000065sys.stdout = SATestUtils.flushfile(sys.stdout)
Anna Zaksde1f7f8b2012-01-10 18:10:25 +000066
George Karpenkova8076602017-10-02 17:59:12 +000067
Anna Zaksf0c41162011-10-06 23:26:27 +000068def getProjectMapPath():
Ted Kremenek3a0678e2015-09-08 03:50:52 +000069 ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
Anna Zaksf0c41162011-10-06 23:26:27 +000070 ProjectMapFile)
71 if not os.path.exists(ProjectMapPath):
72 print "Error: Cannot find the Project Map file " + ProjectMapPath +\
George Karpenkova8076602017-10-02 17:59:12 +000073 "\nRunning script for the wrong directory?"
George Karpenkov65839bd2017-10-26 01:13:22 +000074 sys.exit(1)
Ted Kremenek3a0678e2015-09-08 03:50:52 +000075 return ProjectMapPath
Anna Zaksf0c41162011-10-06 23:26:27 +000076
George Karpenkova8076602017-10-02 17:59:12 +000077
Anna Zaksf0c41162011-10-06 23:26:27 +000078def getProjectDir(ID):
Ted Kremenek3a0678e2015-09-08 03:50:52 +000079 return os.path.join(os.path.abspath(os.curdir), ID)
Anna Zaksf0c41162011-10-06 23:26:27 +000080
George Karpenkova8076602017-10-02 17:59:12 +000081
82def getSBOutputDirName(IsReferenceBuild):
83 if IsReferenceBuild:
Anna Zaks4720a732011-11-05 05:20:48 +000084 return SBOutputDirReferencePrefix + SBOutputDirName
George Karpenkova8076602017-10-02 17:59:12 +000085 else:
Anna Zaks4720a732011-11-05 05:20:48 +000086 return SBOutputDirName
87
Ted Kremenek42c14422012-08-28 20:40:02 +000088#------------------------------------------------------------------------------
89# Configuration setup.
90#------------------------------------------------------------------------------
91
George Karpenkova8076602017-10-02 17:59:12 +000092
Ted Kremenek42c14422012-08-28 20:40:02 +000093# Find Clang for static analysis.
George Karpenkovbe6c3292017-09-21 22:12:49 +000094if 'CC' in os.environ:
95 Clang = os.environ['CC']
96else:
George Karpenkovbf92c442017-10-24 23:52:48 +000097 Clang = SATestUtils.which("clang", os.environ['PATH'])
Ted Kremenek42c14422012-08-28 20:40:02 +000098if not Clang:
99 print "Error: cannot find 'clang' in PATH"
George Karpenkov65839bd2017-10-26 01:13:22 +0000100 sys.exit(1)
Ted Kremenek42c14422012-08-28 20:40:02 +0000101
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000102# Number of jobs.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000103Jobs = int(math.ceil(multiprocessing.cpu_count() * 0.75))
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000104
Ted Kremenek42c14422012-08-28 20:40:02 +0000105# Project map stores info about all the "registered" projects.
106ProjectMapFile = "projectMap.csv"
107
108# Names of the project specific scripts.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000109# The script that downloads the project.
110DownloadScript = "download_project.sh"
Ted Kremenek42c14422012-08-28 20:40:02 +0000111# The script that needs to be executed before the build can start.
112CleanupScript = "cleanup_run_static_analyzer.sh"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000113# This is a file containing commands for scan-build.
Ted Kremenek42c14422012-08-28 20:40:02 +0000114BuildScript = "run_static_analyzer.cmd"
115
116# The log file name.
117LogFolderName = "Logs"
118BuildLogName = "run_static_analyzer.log"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000119# Summary file - contains the summary of the failures. Ex: This info can be be
Ted Kremenek42c14422012-08-28 20:40:02 +0000120# displayed when buildbot detects a build failure.
121NumOfFailuresInSummary = 10
122FailuresSummaryFileName = "failures.txt"
123# Summary of the result diffs.
124DiffsSummaryFileName = "diffs.txt"
125
126# The scan-build result directory.
127SBOutputDirName = "ScanBuildResults"
128SBOutputDirReferencePrefix = "Ref"
129
George Karpenkova8076602017-10-02 17:59:12 +0000130# The name of the directory storing the cached project source. If this
131# directory does not exist, the download script will be executed.
132# That script should create the "CachedSource" directory and download the
133# project source into it.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000134CachedSourceDirName = "CachedSource"
135
136# The name of the directory containing the source code that will be analyzed.
137# Each time a project is analyzed, a fresh copy of its CachedSource directory
138# will be copied to the PatchedSource directory and then the local patches
139# in PatchfileName will be applied (if PatchfileName exists).
140PatchedSourceDirName = "PatchedSource"
141
142# The name of the patchfile specifying any changes that should be applied
143# to the CachedSource before analyzing.
144PatchfileName = "changes_for_analyzer.patch"
145
Ted Kremenek42c14422012-08-28 20:40:02 +0000146# The list of checkers used during analyzes.
Alp Tokerd4733632013-12-05 04:47:09 +0000147# Currently, consists of all the non-experimental checkers, plus a few alpha
Jordan Rose10ad0812013-04-05 17:55:07 +0000148# checkers we don't want to regress on.
George Karpenkova8076602017-10-02 17:59:12 +0000149Checkers = ",".join([
George Karpenkovaf76b4a2017-09-30 00:05:24 +0000150 "alpha.unix.SimpleStream",
151 "alpha.security.taint",
152 "cplusplus.NewDeleteLeaks",
153 "core",
154 "cplusplus",
155 "deadcode",
156 "security",
157 "unix",
158 "osx",
159 "nullability"
160])
Ted Kremenek42c14422012-08-28 20:40:02 +0000161
162Verbose = 1
163
164#------------------------------------------------------------------------------
165# Test harness logic.
166#------------------------------------------------------------------------------
167
George Karpenkova8076602017-10-02 17:59:12 +0000168
Anna Zaks42a44632011-11-02 20:46:50 +0000169def runCleanupScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000170 """
171 Run pre-processing script if any.
172 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000173 Cwd = os.path.join(Dir, PatchedSourceDirName)
Anna Zaks42a44632011-11-02 20:46:50 +0000174 ScriptPath = os.path.join(Dir, CleanupScript)
George Karpenkovbf92c442017-10-24 23:52:48 +0000175 SATestUtils.runScript(ScriptPath, PBuildLogFile, Cwd)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000176
George Karpenkova8076602017-10-02 17:59:12 +0000177
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000178def runDownloadScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000179 """
180 Run the script to download the project, if it exists.
181 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000182 ScriptPath = os.path.join(Dir, DownloadScript)
George Karpenkovbf92c442017-10-24 23:52:48 +0000183 SATestUtils.runScript(ScriptPath, PBuildLogFile, Dir)
Anna Zaksf0c41162011-10-06 23:26:27 +0000184
George Karpenkova8076602017-10-02 17:59:12 +0000185
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000186def downloadAndPatch(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000187 """
188 Download the project and apply the local patchfile if it exists.
189 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000190 CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName)
191
192 # If the we don't already have the cached source, run the project's
193 # download script to download it.
194 if not os.path.exists(CachedSourceDirPath):
George Karpenkova8076602017-10-02 17:59:12 +0000195 runDownloadScript(Dir, PBuildLogFile)
196 if not os.path.exists(CachedSourceDirPath):
197 print "Error: '%s' not found after download." % (
198 CachedSourceDirPath)
George Karpenkov65839bd2017-10-26 01:13:22 +0000199 exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000200
201 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
202
203 # Remove potentially stale patched source.
204 if os.path.exists(PatchedSourceDirPath):
205 shutil.rmtree(PatchedSourceDirPath)
206
207 # Copy the cached source and apply any patches to the copy.
208 shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True)
209 applyPatch(Dir, PBuildLogFile)
210
George Karpenkova8076602017-10-02 17:59:12 +0000211
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000212def applyPatch(Dir, PBuildLogFile):
213 PatchfilePath = os.path.join(Dir, PatchfileName)
214 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
215 if not os.path.exists(PatchfilePath):
216 print " No local patches."
217 return
218
219 print " Applying patch."
220 try:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000221 check_call("patch -p1 < '%s'" % (PatchfilePath),
George Karpenkova8076602017-10-02 17:59:12 +0000222 cwd=PatchedSourceDirPath,
223 stderr=PBuildLogFile,
224 stdout=PBuildLogFile,
225 shell=True)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000226 except:
227 print "Error: Patch failed. See %s for details." % (PBuildLogFile.name)
George Karpenkov65839bd2017-10-26 01:13:22 +0000228 sys.exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000229
George Karpenkova8076602017-10-02 17:59:12 +0000230
Anna Zaksf0c41162011-10-06 23:26:27 +0000231def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000232 """
233 Build the project with scan-build by reading in the commands and
234 prefixing them with the scan-build options.
235 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000236 BuildScriptPath = os.path.join(Dir, BuildScript)
237 if not os.path.exists(BuildScriptPath):
238 print "Error: build script is not defined: %s" % BuildScriptPath
George Karpenkov65839bd2017-10-26 01:13:22 +0000239 sys.exit(1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000240
241 AllCheckers = Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000242 if 'SA_ADDITIONAL_CHECKERS' in os.environ:
Devin Coughlinf3695c82015-09-16 01:52:32 +0000243 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
244
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000245 # Run scan-build from within the patched source directory.
246 SBCwd = os.path.join(Dir, PatchedSourceDirName)
247
George Karpenkova8076602017-10-02 17:59:12 +0000248 SBOptions = "--use-analyzer '%s' " % Clang
Devin Coughlin86f61a92016-01-22 18:45:22 +0000249 SBOptions += "-plist-html -o '%s' " % SBOutputDir
Devin Coughlinf3695c82015-09-16 01:52:32 +0000250 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000251 SBOptions += "--keep-empty "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000252 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000253 # directory.
254 SBOptions += "--override-compiler "
Anna Zaksf0c41162011-10-06 23:26:27 +0000255 try:
256 SBCommandFile = open(BuildScriptPath, "r")
257 SBPrefix = "scan-build " + SBOptions + " "
258 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000259 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000260 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000261 continue
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000262 # If using 'make', auto imply a -jX argument
263 # to speed up analysis. xcodebuild will
264 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000265 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000266 "-j" not in Command:
Jordan Rose88329242012-11-16 17:41:21 +0000267 Command += " -j%d" % Jobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000268 SBCommand = SBPrefix + Command
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000269 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000270 print " Executing: %s" % (SBCommand,)
George Karpenkova8076602017-10-02 17:59:12 +0000271 check_call(SBCommand, cwd=SBCwd,
272 stderr=PBuildLogFile,
273 stdout=PBuildLogFile,
274 shell=True)
George Karpenkove58044d2017-10-27 22:39:54 +0000275 except CalledProcessError:
276 print "Error: scan-build failed. Its output was: "
277 PBuildLogFile.seek(0)
278 shutil.copyfileobj(PBuildLogFile, sys.stdout)
279 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000280
George Karpenkova8076602017-10-02 17:59:12 +0000281
Anna Zaksa2f970b2012-09-06 23:30:27 +0000282def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000283 """
284 Run analysis on a set of preprocessed files.
285 """
Anna Zaks4720a732011-11-05 05:20:48 +0000286 if os.path.exists(os.path.join(Dir, BuildScript)):
287 print "Error: The preprocessed files project should not contain %s" % \
George Karpenkova8076602017-10-02 17:59:12 +0000288 BuildScript
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000289 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000290
Devin Coughlinbace0322015-09-14 21:22:24 +0000291 CmdPrefix = Clang + " -cc1 "
292
293 # For now, we assume the preprocessed files should be analyzed
294 # with the OS X SDK.
George Karpenkovbf92c442017-10-24 23:52:48 +0000295 SDKPath = SATestUtils.getSDKPath("macosx")
Devin Coughlinbace0322015-09-14 21:22:24 +0000296 if SDKPath is not None:
George Karpenkova8076602017-10-02 17:59:12 +0000297 CmdPrefix += "-isysroot " + SDKPath + " "
Devin Coughlinbace0322015-09-14 21:22:24 +0000298
299 CmdPrefix += "-analyze -analyzer-output=plist -w "
George Karpenkova8076602017-10-02 17:59:12 +0000300 CmdPrefix += "-analyzer-checker=" + Checkers
301 CmdPrefix += " -fcxx-exceptions -fblocks "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000302
George Karpenkova8076602017-10-02 17:59:12 +0000303 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000304 CmdPrefix += "-std=c++11 "
305
Anna Zaks4720a732011-11-05 05:20:48 +0000306 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000307 FailPath = os.path.join(PlistPath, "failures")
308 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000309
Anna Zaks4720a732011-11-05 05:20:48 +0000310 for FullFileName in glob.glob(Dir + "/*"):
311 FileName = os.path.basename(FullFileName)
312 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000313
Anna Zaks4720a732011-11-05 05:20:48 +0000314 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000315 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000316 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000317 if not SATestUtils.isValidSingleInputFile(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000318 print "Error: Invalid single input file %s." % (FullFileName,)
319 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000320
Anna Zaks4720a732011-11-05 05:20:48 +0000321 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000322 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
323 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000324 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
325 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000326 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000327 print " Executing: %s" % (Command,)
George Karpenkova8076602017-10-02 17:59:12 +0000328 check_call(Command, cwd=Dir, stderr=LogFile,
329 stdout=LogFile,
330 shell=True)
Anna Zaks4720a732011-11-05 05:20:48 +0000331 except CalledProcessError, e:
332 print "Error: Analyzes of %s failed. See %s for details." \
George Karpenkova8076602017-10-02 17:59:12 +0000333 "Error code %d." % (
334 FullFileName, LogFile.name, e.returncode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000335 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000336 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000337 LogFile.close()
338
Anna Zaks4720a732011-11-05 05:20:48 +0000339 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000340 if not Failed:
341 os.remove(LogFile.name)
342
Anna Zaks4720a732011-11-05 05:20:48 +0000343
Devin Coughlin9ea80332016-01-23 01:09:07 +0000344def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000345 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
346
Devin Coughlin9ea80332016-01-23 01:09:07 +0000347
348def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000349 BuildLogPath = getBuildLogPath(SBOutputDir)
350 # Clean up the log file.
351 if (os.path.exists(BuildLogPath)):
352 RmCommand = "rm '%s'" % BuildLogPath
353 if Verbose == 1:
354 print " Executing: %s" % (RmCommand,)
355 check_call(RmCommand, shell=True)
356
Devin Coughlin9ea80332016-01-23 01:09:07 +0000357
Anna Zaksa2f970b2012-09-06 23:30:27 +0000358def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000359 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000360
Devin Coughlin9ea80332016-01-23 01:09:07 +0000361 BuildLogPath = getBuildLogPath(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000362 print "Log file: %s" % (BuildLogPath,)
George Karpenkova8076602017-10-02 17:59:12 +0000363 print "Output directory: %s" % (SBOutputDir, )
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000364
Devin Coughlin9ea80332016-01-23 01:09:07 +0000365 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000366
Anna Zaks4720a732011-11-05 05:20:48 +0000367 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000368 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000369 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000370 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000371 print " Executing: %s" % (RmCommand,)
372 check_call(RmCommand, shell=True)
373 assert(not os.path.exists(SBOutputDir))
374 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000375
Anna Zaks4720a732011-11-05 05:20:48 +0000376 # Build and analyze the project.
George Karpenkove58044d2017-10-27 22:39:54 +0000377 with open(BuildLogPath, "r+b") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000378 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000379 downloadAndPatch(Dir, PBuildLogFile)
380 runCleanupScript(Dir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000381 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
382 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000383 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000384
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000385 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000386 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000387 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000388
Anna Zaksf0c41162011-10-06 23:26:27 +0000389 print "Build complete (time: %.2f). See the log for more details: %s" % \
George Karpenkova8076602017-10-02 17:59:12 +0000390 ((time.time() - TBegin), BuildLogPath)
391
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000392
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000393def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
394 """
395 Make the absolute paths relative in the reference results.
396 """
397 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
398 for F in Filenames:
399 if (not F.endswith('plist')):
400 continue
401 Plist = os.path.join(DirPath, F)
402 Data = plistlib.readPlist(Plist)
403 PathPrefix = Dir
404 if (ProjectBuildMode == 1):
405 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000406 Paths = [SourceFile[len(PathPrefix) + 1:]
407 if SourceFile.startswith(PathPrefix)
408 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000409 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000410
411 # Remove transient fields which change from run to run.
412 for Diag in Data['diagnostics']:
413 if 'HTMLDiagnostics_files' in Diag:
414 Diag.pop('HTMLDiagnostics_files')
415 if 'clang_version' in Data:
416 Data.pop('clang_version')
417
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000418 plistlib.writePlist(Data, Plist)
419
George Karpenkova8076602017-10-02 17:59:12 +0000420
Anna Zaksf0c41162011-10-06 23:26:27 +0000421def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000422 """
423 A plist file is created for each call to the analyzer(each source file).
424 We are only interested on the once that have bug reports,
425 so delete the rest.
426 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000427 for F in glob.glob(SBOutputDir + "/*/*.plist"):
428 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000429
Anna Zaksf0c41162011-10-06 23:26:27 +0000430 Data = plistlib.readPlist(P)
431 # Delete empty reports.
432 if not Data['files']:
433 os.remove(P)
434 continue
435
George Karpenkova8076602017-10-02 17:59:12 +0000436
Anna Zaksf0c41162011-10-06 23:26:27 +0000437def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000438 """
439 Given the scan-build output directory, checks if the build failed
440 (by searching for the failures directories). If there are failures, it
441 creates a summary file in the output directory.
442
443 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000444 # Check if there are failures.
445 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000446 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000447 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000448 CleanUpEmptyPlists(SBOutputDir)
449 Plists = glob.glob(SBOutputDir + "/*/*.plist")
Alp Tokerd4733632013-12-05 04:47:09 +0000450 print "Number of bug reports (non-empty plist files) produced: %d" %\
George Karpenkova8076602017-10-02 17:59:12 +0000451 len(Plists)
452 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000453
George Karpenkovff555ce2017-10-26 19:00:22 +0000454 print "Error: analysis failed."
455 print "Total of %d failures discovered." % TotalFailed
456 if TotalFailed > NumOfFailuresInSummary:
457 print "See the first %d below.\n" % NumOfFailuresInSummary
Anna Zaksf0c41162011-10-06 23:26:27 +0000458 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000459
George Karpenkovff555ce2017-10-26 19:00:22 +0000460 Idx = 0
461 for FailLogPathI in Failures:
462 if Idx >= NumOfFailuresInSummary:
463 break
464 Idx += 1
465 print "\n-- Error #%d -----------\n" % Idx
466 with open(FailLogPathI, "r") as FailLogI:
467 shutil.copyfileobj(FailLogI, sys.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000468
George Karpenkov65839bd2017-10-26 01:13:22 +0000469 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000470
Anna Zaksf0c41162011-10-06 23:26:27 +0000471
George Karpenkova8076602017-10-02 17:59:12 +0000472def runCmpResults(Dir, Strictness=0):
473 """
474 Compare the warnings produced by scan-build.
475 Strictness defines the success criteria for the test:
476 0 - success if there are no crashes or analyzer failure.
477 1 - success if there are no difference in the number of reported bugs.
478 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000479
480 :return success: Whether tests pass according to the Strictness
481 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000482 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000483 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000484 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000485
486 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
487 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000488
Anna Zaksf0c41162011-10-06 23:26:27 +0000489 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000490 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000491 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000492
Jordan Rosec7b992e2013-06-10 19:34:30 +0000493 # Log folders are also located in the results dir, so ignore them.
494 RefLogDir = os.path.join(RefDir, LogFolderName)
495 if RefLogDir in RefList:
496 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000497 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000498
George Karpenkov65839bd2017-10-26 01:13:22 +0000499 if len(RefList) != len(NewList):
500 print "Mismatch in number of results folders: %s vs %s" % (
501 RefList, NewList)
502 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000503
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000504 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000505 # command (Ex: one for configure and one for make).
506 if (len(RefList) > 1):
507 # Assume that the corresponding folders have the same names.
508 RefList.sort()
509 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000510
Anna Zaksf0c41162011-10-06 23:26:27 +0000511 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000512 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000513 PairList = zip(RefList, NewList)
514 for P in PairList:
515 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000516 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000517
518 assert(RefDir != NewDir)
519 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000520 print " Comparing Results: %s %s" % (RefDir, NewDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000521
Anna Zaksf0c41162011-10-06 23:26:27 +0000522 DiffsPath = os.path.join(NewDir, DiffsSummaryFileName)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000523 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
524 Opts = CmpRuns.CmpOptions(DiffsPath, "", PatchedSourceDirPath)
Anna Zaksf0c41162011-10-06 23:26:27 +0000525 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000526 NumDiffs, ReportsInRef, ReportsInNew = \
527 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False)
George Karpenkova8076602017-10-02 17:59:12 +0000528 if (NumDiffs > 0):
George Karpenkova932c872017-10-25 21:49:46 +0000529 print "Warning: %s differences in diagnostics." % NumDiffs
Gabor Horvath93fde942015-06-30 15:31:17 +0000530 if Strictness >= 2 and NumDiffs > 0:
531 print "Error: Diffs found in strict mode (2)."
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000532 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000533 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkova8076602017-10-02 17:59:12 +0000534 print "Error: The number of results are different in "\
535 "strict mode (1)."
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000536 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000537
George Karpenkova8076602017-10-02 17:59:12 +0000538 print "Diagnostic comparison complete (time: %.2f)." % (
539 time.time() - TBegin)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000540 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000541
George Karpenkova8076602017-10-02 17:59:12 +0000542
Devin Coughlin9ea80332016-01-23 01:09:07 +0000543def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000544 """
545 Delete html, css, and js files from reference results. These can
546 include multiple copies of the benchmark source and so get very large.
547 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000548 Extensions = ["html", "css", "js"]
549 for E in Extensions:
550 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
551 P = os.path.join(SBOutputDir, F)
552 RmCommand = "rm '%s'" % P
553 check_call(RmCommand, shell=True)
554
555 # Remove the log file. It leaks absolute path names.
556 removeLogFile(SBOutputDir)
557
George Karpenkova8076602017-10-02 17:59:12 +0000558
George Karpenkova8076602017-10-02 17:59:12 +0000559def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000560 """
561 Test a given project.
562 :return TestsPassed: Whether tests have passed according
563 to the :param Strictness: criteria.
564 """
Anna Zaks4720a732011-11-05 05:20:48 +0000565 print " \n\n--- Building project %s" % (ID,)
566
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000567 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000568
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000569 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000570 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000571 print " Build directory: %s." % (Dir,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000572
Anna Zaksf0c41162011-10-06 23:26:27 +0000573 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000574 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000575 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000576
Anna Zaksa2f970b2012-09-06 23:30:27 +0000577 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000578
579 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000580
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000581 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000582 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000583 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000584 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000585 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000586
Anna Zaksf0c41162011-10-06 23:26:27 +0000587 print "Completed tests for project %s (time: %.2f)." % \
George Karpenkova8076602017-10-02 17:59:12 +0000588 (ID, (time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000589 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000590
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000591
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000592def projectFileHandler():
593 return open(getProjectMapPath(), "rb")
594
George Karpenkova8076602017-10-02 17:59:12 +0000595
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000596def iterateOverProjects(PMapFile):
597 """
598 Iterate over all projects defined in the project file handler `PMapFile`
599 from the start.
600 """
601 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000602 for I in csv.reader(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000603 if (SATestUtils.isCommentCSVLine(I)):
George Karpenkova8076602017-10-02 17:59:12 +0000604 continue
605 yield I
606
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000607
608def validateProjectFile(PMapFile):
609 """
610 Validate project file.
611 """
612 for I in iterateOverProjects(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000613 if len(I) != 2:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000614 print "Error: Rows in the ProjectMapFile should have 2 entries."
615 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000616 if I[1] not in ('0', '1', '2'):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000617 print "Error: Second entry in the ProjectMapFile should be 0" \
George Karpenkova8076602017-10-02 17:59:12 +0000618 " (single file), 1 (project), or 2(single file c++11)."
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000619 raise Exception()
620
George Karpenkova8076602017-10-02 17:59:12 +0000621
George Karpenkov7d36cd72017-10-11 18:42:39 +0000622def testAll(IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000623 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000624 with projectFileHandler() as PMapFile:
625 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000626
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000627 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000628 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000629 TestsPassed &= testProject(
George Karpenkova8076602017-10-02 17:59:12 +0000630 ProjName, int(ProjBuildMode), IsReferenceBuild, Strictness)
George Karpenkov43f683c2017-10-24 22:24:13 +0000631 return TestsPassed
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000632
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000633
Anna Zaksf0c41162011-10-06 23:26:27 +0000634if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000635 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000636 Parser = argparse.ArgumentParser(
637 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000638 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000639 help='0 to fail on runtime errors, 1 to fail when the \
640 number of found bugs are different from the \
641 reference, 2 to fail on any difference from the \
642 reference. Default is 0.')
643 Parser.add_argument('-r', dest='regenerate', action='store_true',
644 default=False, help='Regenerate reference output.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000645 Args = Parser.parse_args()
646
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000647 IsReference = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000648 Strictness = Args.strictness
649 if Args.regenerate:
650 IsReference = True
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000651
George Karpenkov7d36cd72017-10-11 18:42:39 +0000652 TestsPassed = testAll(IsReference, Strictness)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000653 if not TestsPassed:
George Karpenkovbf92c442017-10-24 23:52:48 +0000654 print "ERROR: Tests failed."
George Karpenkov65839bd2017-10-26 01:13:22 +0000655 sys.exit(42)