blob: 2cf02ea64afda4f1ac2ffb35f1c3f647f1e6283c [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?"
Ted Kremenek3a0678e2015-09-08 03:50:52 +000074 sys.exit(-1)
75 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"
100 sys.exit(-1)
101
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)
199 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)
228 sys.exit(-1)
229
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
Ted Kremenek6cb20802012-08-28 20:20:52 +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)
Anna Zaksf0c41162011-10-06 23:26:27 +0000275 except:
George Karpenkova8076602017-10-02 17:59:12 +0000276 print "Error: scan-build failed. See ", PBuildLogFile.name,\
Anna Zaksf0c41162011-10-06 23:26:27 +0000277 " for details."
Anna Zaks4720a732011-11-05 05:20:48 +0000278 raise
Anna Zaksf0c41162011-10-06 23:26:27 +0000279
George Karpenkova8076602017-10-02 17:59:12 +0000280
Anna Zaksa2f970b2012-09-06 23:30:27 +0000281def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000282 """
283 Run analysis on a set of preprocessed files.
284 """
Anna Zaks4720a732011-11-05 05:20:48 +0000285 if os.path.exists(os.path.join(Dir, BuildScript)):
286 print "Error: The preprocessed files project should not contain %s" % \
George Karpenkova8076602017-10-02 17:59:12 +0000287 BuildScript
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000288 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000289
Devin Coughlinbace0322015-09-14 21:22:24 +0000290 CmdPrefix = Clang + " -cc1 "
291
292 # For now, we assume the preprocessed files should be analyzed
293 # with the OS X SDK.
George Karpenkovbf92c442017-10-24 23:52:48 +0000294 SDKPath = SATestUtils.getSDKPath("macosx")
Devin Coughlinbace0322015-09-14 21:22:24 +0000295 if SDKPath is not None:
George Karpenkova8076602017-10-02 17:59:12 +0000296 CmdPrefix += "-isysroot " + SDKPath + " "
Devin Coughlinbace0322015-09-14 21:22:24 +0000297
298 CmdPrefix += "-analyze -analyzer-output=plist -w "
George Karpenkova8076602017-10-02 17:59:12 +0000299 CmdPrefix += "-analyzer-checker=" + Checkers
300 CmdPrefix += " -fcxx-exceptions -fblocks "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000301
George Karpenkova8076602017-10-02 17:59:12 +0000302 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000303 CmdPrefix += "-std=c++11 "
304
Anna Zaks4720a732011-11-05 05:20:48 +0000305 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000306 FailPath = os.path.join(PlistPath, "failures")
307 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000308
Anna Zaks4720a732011-11-05 05:20:48 +0000309 for FullFileName in glob.glob(Dir + "/*"):
310 FileName = os.path.basename(FullFileName)
311 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000312
Anna Zaks4720a732011-11-05 05:20:48 +0000313 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000314 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000315 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000316 if not SATestUtils.isValidSingleInputFile(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000317 print "Error: Invalid single input file %s." % (FullFileName,)
318 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000319
Anna Zaks4720a732011-11-05 05:20:48 +0000320 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000321 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
322 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000323 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
324 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000325 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000326 print " Executing: %s" % (Command,)
George Karpenkova8076602017-10-02 17:59:12 +0000327 check_call(Command, cwd=Dir, stderr=LogFile,
328 stdout=LogFile,
329 shell=True)
Anna Zaks4720a732011-11-05 05:20:48 +0000330 except CalledProcessError, e:
331 print "Error: Analyzes of %s failed. See %s for details." \
George Karpenkova8076602017-10-02 17:59:12 +0000332 "Error code %d." % (
333 FullFileName, LogFile.name, e.returncode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000334 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000335 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000336 LogFile.close()
337
Anna Zaks4720a732011-11-05 05:20:48 +0000338 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000339 if not Failed:
340 os.remove(LogFile.name)
341
Anna Zaks4720a732011-11-05 05:20:48 +0000342
Devin Coughlin9ea80332016-01-23 01:09:07 +0000343def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000344 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
345
Devin Coughlin9ea80332016-01-23 01:09:07 +0000346
347def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000348 BuildLogPath = getBuildLogPath(SBOutputDir)
349 # Clean up the log file.
350 if (os.path.exists(BuildLogPath)):
351 RmCommand = "rm '%s'" % BuildLogPath
352 if Verbose == 1:
353 print " Executing: %s" % (RmCommand,)
354 check_call(RmCommand, shell=True)
355
Devin Coughlin9ea80332016-01-23 01:09:07 +0000356
Anna Zaksa2f970b2012-09-06 23:30:27 +0000357def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000358 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000359
Devin Coughlin9ea80332016-01-23 01:09:07 +0000360 BuildLogPath = getBuildLogPath(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000361 print "Log file: %s" % (BuildLogPath,)
George Karpenkova8076602017-10-02 17:59:12 +0000362 print "Output directory: %s" % (SBOutputDir, )
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000363
Devin Coughlin9ea80332016-01-23 01:09:07 +0000364 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000365
Anna Zaks4720a732011-11-05 05:20:48 +0000366 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000367 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000368 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000369 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000370 print " Executing: %s" % (RmCommand,)
371 check_call(RmCommand, shell=True)
372 assert(not os.path.exists(SBOutputDir))
373 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000374
Anna Zaks4720a732011-11-05 05:20:48 +0000375 # Build and analyze the project.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000376 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000377 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000378 downloadAndPatch(Dir, PBuildLogFile)
379 runCleanupScript(Dir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000380 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
381 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000382 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000383
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000384 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000385 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000386 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000387
Anna Zaksf0c41162011-10-06 23:26:27 +0000388 print "Build complete (time: %.2f). See the log for more details: %s" % \
George Karpenkova8076602017-10-02 17:59:12 +0000389 ((time.time() - TBegin), BuildLogPath)
390
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000391
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000392def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
393 """
394 Make the absolute paths relative in the reference results.
395 """
396 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
397 for F in Filenames:
398 if (not F.endswith('plist')):
399 continue
400 Plist = os.path.join(DirPath, F)
401 Data = plistlib.readPlist(Plist)
402 PathPrefix = Dir
403 if (ProjectBuildMode == 1):
404 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000405 Paths = [SourceFile[len(PathPrefix) + 1:]
406 if SourceFile.startswith(PathPrefix)
407 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000408 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000409
410 # Remove transient fields which change from run to run.
411 for Diag in Data['diagnostics']:
412 if 'HTMLDiagnostics_files' in Diag:
413 Diag.pop('HTMLDiagnostics_files')
414 if 'clang_version' in Data:
415 Data.pop('clang_version')
416
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000417 plistlib.writePlist(Data, Plist)
418
George Karpenkova8076602017-10-02 17:59:12 +0000419
Anna Zaksf0c41162011-10-06 23:26:27 +0000420def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000421 """
422 A plist file is created for each call to the analyzer(each source file).
423 We are only interested on the once that have bug reports,
424 so delete the rest.
425 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000426 for F in glob.glob(SBOutputDir + "/*/*.plist"):
427 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000428
Anna Zaksf0c41162011-10-06 23:26:27 +0000429 Data = plistlib.readPlist(P)
430 # Delete empty reports.
431 if not Data['files']:
432 os.remove(P)
433 continue
434
George Karpenkova8076602017-10-02 17:59:12 +0000435
Anna Zaksf0c41162011-10-06 23:26:27 +0000436def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000437 """
438 Given the scan-build output directory, checks if the build failed
439 (by searching for the failures directories). If there are failures, it
440 creates a summary file in the output directory.
441
442 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000443 # Check if there are failures.
444 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000445 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000446 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000447 CleanUpEmptyPlists(SBOutputDir)
448 Plists = glob.glob(SBOutputDir + "/*/*.plist")
Alp Tokerd4733632013-12-05 04:47:09 +0000449 print "Number of bug reports (non-empty plist files) produced: %d" %\
George Karpenkova8076602017-10-02 17:59:12 +0000450 len(Plists)
451 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000452
Anna Zaksf0c41162011-10-06 23:26:27 +0000453 # Create summary file to display when the build fails.
George Karpenkova8076602017-10-02 17:59:12 +0000454 SummaryPath = os.path.join(
455 SBOutputDir, LogFolderName, FailuresSummaryFileName)
Anna Zaksf0c41162011-10-06 23:26:27 +0000456 if (Verbose > 0):
Anna Zaks4720a732011-11-05 05:20:48 +0000457 print " Creating the failures summary file %s" % (SummaryPath,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000458
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000459 with open(SummaryPath, "w+") as SummaryLog:
Anna Zaksf0c41162011-10-06 23:26:27 +0000460 SummaryLog.write("Total of %d failures discovered.\n" % (TotalFailed,))
461 if TotalFailed > NumOfFailuresInSummary:
George Karpenkova8076602017-10-02 17:59:12 +0000462 SummaryLog.write("See the first %d below.\n" % (
463 NumOfFailuresInSummary,))
Anna Zaksf0c41162011-10-06 23:26:27 +0000464 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000465
Anna Zaksf0c41162011-10-06 23:26:27 +0000466 Idx = 0
Jordan Rosedc191a12012-06-01 16:24:43 +0000467 for FailLogPathI in Failures:
Anna Zaksf0c41162011-10-06 23:26:27 +0000468 if Idx >= NumOfFailuresInSummary:
George Karpenkova8076602017-10-02 17:59:12 +0000469 break
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000470 Idx += 1
George Karpenkova8076602017-10-02 17:59:12 +0000471 SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,))
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000472 with open(FailLogPathI, "r") as FailLogI:
George Karpenkova8076602017-10-02 17:59:12 +0000473 shutil.copyfileobj(FailLogI, SummaryLog)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000474
Anna Zaks5acd9602012-01-04 23:53:50 +0000475 print "Error: analysis failed. See ", SummaryPath
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000476 sys.exit(-1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000477
Anna Zaksf0c41162011-10-06 23:26:27 +0000478
George Karpenkova8076602017-10-02 17:59:12 +0000479def runCmpResults(Dir, Strictness=0):
480 """
481 Compare the warnings produced by scan-build.
482 Strictness defines the success criteria for the test:
483 0 - success if there are no crashes or analyzer failure.
484 1 - success if there are no difference in the number of reported bugs.
485 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000486
487 :return success: Whether tests pass according to the Strictness
488 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000489 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000490 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000491 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000492
493 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
494 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000495
Anna Zaksf0c41162011-10-06 23:26:27 +0000496 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000497 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000498 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000499
Jordan Rosec7b992e2013-06-10 19:34:30 +0000500 # Log folders are also located in the results dir, so ignore them.
501 RefLogDir = os.path.join(RefDir, LogFolderName)
502 if RefLogDir in RefList:
503 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000504 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000505
Anna Zaksf0c41162011-10-06 23:26:27 +0000506 assert(len(RefList) == len(NewList))
507
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000508 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000509 # command (Ex: one for configure and one for make).
510 if (len(RefList) > 1):
511 # Assume that the corresponding folders have the same names.
512 RefList.sort()
513 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000514
Anna Zaksf0c41162011-10-06 23:26:27 +0000515 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000516 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000517 PairList = zip(RefList, NewList)
518 for P in PairList:
519 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000520 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000521
522 assert(RefDir != NewDir)
523 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000524 print " Comparing Results: %s %s" % (RefDir, NewDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000525
Anna Zaksf0c41162011-10-06 23:26:27 +0000526 DiffsPath = os.path.join(NewDir, DiffsSummaryFileName)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000527 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
528 Opts = CmpRuns.CmpOptions(DiffsPath, "", PatchedSourceDirPath)
Anna Zaksf0c41162011-10-06 23:26:27 +0000529 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000530 NumDiffs, ReportsInRef, ReportsInNew = \
531 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False)
George Karpenkova8076602017-10-02 17:59:12 +0000532 if (NumDiffs > 0):
George Karpenkova932c872017-10-25 21:49:46 +0000533 print "Warning: %s differences in diagnostics." % NumDiffs
Gabor Horvath93fde942015-06-30 15:31:17 +0000534 if Strictness >= 2 and NumDiffs > 0:
535 print "Error: Diffs found in strict mode (2)."
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000536 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000537 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkova8076602017-10-02 17:59:12 +0000538 print "Error: The number of results are different in "\
539 "strict mode (1)."
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000540 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000541
George Karpenkova8076602017-10-02 17:59:12 +0000542 print "Diagnostic comparison complete (time: %.2f)." % (
543 time.time() - TBegin)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000544 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000545
George Karpenkova8076602017-10-02 17:59:12 +0000546
Devin Coughlin9ea80332016-01-23 01:09:07 +0000547def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000548 """
549 Delete html, css, and js files from reference results. These can
550 include multiple copies of the benchmark source and so get very large.
551 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000552 Extensions = ["html", "css", "js"]
553 for E in Extensions:
554 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
555 P = os.path.join(SBOutputDir, F)
556 RmCommand = "rm '%s'" % P
557 check_call(RmCommand, shell=True)
558
559 # Remove the log file. It leaks absolute path names.
560 removeLogFile(SBOutputDir)
561
George Karpenkova8076602017-10-02 17:59:12 +0000562
George Karpenkova8076602017-10-02 17:59:12 +0000563def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000564 """
565 Test a given project.
566 :return TestsPassed: Whether tests have passed according
567 to the :param Strictness: criteria.
568 """
Anna Zaks4720a732011-11-05 05:20:48 +0000569 print " \n\n--- Building project %s" % (ID,)
570
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000571 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000572
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000573 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000574 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000575 print " Build directory: %s." % (Dir,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000576
Anna Zaksf0c41162011-10-06 23:26:27 +0000577 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000578 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000579 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000580
Anna Zaksa2f970b2012-09-06 23:30:27 +0000581 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000582
583 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000584
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000585 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000586 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000587 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000588 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000589 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000590
Anna Zaksf0c41162011-10-06 23:26:27 +0000591 print "Completed tests for project %s (time: %.2f)." % \
George Karpenkova8076602017-10-02 17:59:12 +0000592 (ID, (time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000593 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000594
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000595
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000596def projectFileHandler():
597 return open(getProjectMapPath(), "rb")
598
George Karpenkova8076602017-10-02 17:59:12 +0000599
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000600def iterateOverProjects(PMapFile):
601 """
602 Iterate over all projects defined in the project file handler `PMapFile`
603 from the start.
604 """
605 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000606 for I in csv.reader(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000607 if (SATestUtils.isCommentCSVLine(I)):
George Karpenkova8076602017-10-02 17:59:12 +0000608 continue
609 yield I
610
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000611
612def validateProjectFile(PMapFile):
613 """
614 Validate project file.
615 """
616 for I in iterateOverProjects(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000617 if len(I) != 2:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000618 print "Error: Rows in the ProjectMapFile should have 2 entries."
619 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000620 if I[1] not in ('0', '1', '2'):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000621 print "Error: Second entry in the ProjectMapFile should be 0" \
George Karpenkova8076602017-10-02 17:59:12 +0000622 " (single file), 1 (project), or 2(single file c++11)."
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000623 raise Exception()
624
George Karpenkova8076602017-10-02 17:59:12 +0000625
George Karpenkov7d36cd72017-10-11 18:42:39 +0000626def testAll(IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000627 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000628 with projectFileHandler() as PMapFile:
629 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000630
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000631 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000632 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000633 TestsPassed &= testProject(
George Karpenkova8076602017-10-02 17:59:12 +0000634 ProjName, int(ProjBuildMode), IsReferenceBuild, Strictness)
George Karpenkov43f683c2017-10-24 22:24:13 +0000635 return TestsPassed
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000636
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000637
Anna Zaksf0c41162011-10-06 23:26:27 +0000638if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000639 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000640 Parser = argparse.ArgumentParser(
641 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000642 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000643 help='0 to fail on runtime errors, 1 to fail when the \
644 number of found bugs are different from the \
645 reference, 2 to fail on any difference from the \
646 reference. Default is 0.')
647 Parser.add_argument('-r', dest='regenerate', action='store_true',
648 default=False, help='Regenerate reference output.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000649 Args = Parser.parse_args()
650
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000651 IsReference = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000652 Strictness = Args.strictness
653 if Args.regenerate:
654 IsReference = True
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000655
George Karpenkov7d36cd72017-10-11 18:42:39 +0000656 TestsPassed = testAll(IsReference, Strictness)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000657 if not TestsPassed:
George Karpenkovbf92c442017-10-24 23:52:48 +0000658 print "ERROR: Tests failed."
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000659 sys.exit(-1)