-
Notifications
You must be signed in to change notification settings - Fork 398
/
Copy pathanalysis_manager.py
852 lines (683 loc) · 30.9 KB
/
analysis_manager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
"""
"""
import glob
import os
import shlex
import shutil
import signal
import sys
import time
import traceback
import zipfile
from threading import Timer
import multiprocess
import psutil
from codechecker_common.logger import get_logger
from codechecker_common.review_status_handler import ReviewStatusHandler
from codechecker_statistics_collector.collectors.special_return_value import \
SpecialReturnValueCollector
from . import gcc_toolchain
from .analyzers import analyzer_types
from .analyzers.config_handler import CheckerState
from .analyzers.clangsa.analyzer import ClangSA
LOG = get_logger('analyzer')
def print_analyzer_statistic_summary(metadata_analyzers, status, msg=None):
"""
Print analyzer statistic summary for the given status code with the given
section heading message.
"""
has_status = False
for _, analyzer in metadata_analyzers.items():
if analyzer.get('analyzer_statistics', {}).get(status):
has_status = True
break
if has_status and msg:
LOG.info(msg)
for analyzer_type, analyzer in metadata_analyzers.items():
res = analyzer.get('analyzer_statistics', {}).get(status)
if res:
LOG.info(" %s: %s", analyzer_type, res)
def worker_result_handler(results, metadata_tool, output_path):
""" Print the analysis summary. """
skipped_num = 0
reanalyzed_num = 0
metadata_analyzers = metadata_tool['analyzers']
for res, skipped, reanalyzed, analyzer_type, _, sources in results:
statistics = metadata_analyzers[analyzer_type]['analyzer_statistics']
if skipped:
skipped_num += 1
else:
if reanalyzed:
reanalyzed_num += 1
if res == 0:
statistics['successful'] += 1
statistics['successful_sources'].append(sources)
else:
statistics['failed'] += 1
statistics['failed_sources'].append(sources)
LOG.info("----==== Summary ====----")
print_analyzer_statistic_summary(metadata_analyzers,
'successful',
'Successfully analyzed')
print_analyzer_statistic_summary(metadata_analyzers,
'failed',
'Failed to analyze')
if reanalyzed_num:
LOG.info("Reanalyzed compilation commands: %d", reanalyzed_num)
if skipped_num:
LOG.info("Skipped compilation commands: %d", skipped_num)
metadata_tool['skipped'] = skipped_num
# check() created the result .plist files and additional, per-analysis
# meta information in forms of .plist.source files.
# We now soak these files into the metadata dict, as they are not needed
# as loose files on the disk... but synchronizing LARGE dicts between
# threads would be more error prone.
source_map = {}
for f in glob.glob(os.path.join(output_path, "*.source")):
with open(f, 'r', encoding="utf-8", errors="ignore") as sfile:
source_map[f[:-7]] = sfile.read().strip()
os.remove(f)
for f in glob.glob(os.path.join(output_path, 'failed', "*.error")):
err_file, _ = os.path.splitext(f)
plist_file = os.path.basename(err_file) + ".plist"
plist_file = os.path.join(output_path, plist_file)
metadata_tool['result_source_files'].pop(plist_file, None)
metadata_tool['result_source_files'].update(source_map)
# Progress reporting.
progress_checked_num = None
progress_actions = None
def init_worker(checked_num, action_num):
global progress_checked_num, progress_actions
progress_checked_num = checked_num
progress_actions = action_num
def save_output(base_file_name, out, err):
try:
if out:
with open(base_file_name + ".stdout.txt", 'w',
encoding="utf-8", errors="ignore") as outf:
outf.write(out)
if err:
with open(base_file_name + ".stderr.txt", 'w',
encoding="utf-8", errors="ignore") as outf:
outf.write(err)
except IOError as ioerr:
LOG.debug("Failed to save analyzer output")
LOG.debug(ioerr)
def save_metadata(result_file, analyzer_result_file, analyzed_source_file):
"""
Save some extra information next to the plist, .source
acting as an extra metadata file.
"""
with open(result_file + ".source", 'w',
encoding="utf-8", errors="ignore") as orig:
orig.write(analyzed_source_file.replace(r'\ ', ' ') + "\n")
if os.path.exists(analyzer_result_file) and \
not os.path.exists(result_file):
os.rename(analyzer_result_file, result_file)
def is_ctu_active(source_analyzer):
"""
Check if CTU analysis is active for Clang Static Analyzer.
"""
if not isinstance(source_analyzer, ClangSA):
return False
return source_analyzer.is_ctu_available() and \
source_analyzer.is_ctu_enabled()
def prepare_check(action, analyzer_config, output_dir,
skip_handlers, statistics_data, disable_ctu=False):
""" Construct the source analyzer and result handler. """
# Create a source analyzer.
source_analyzer = \
analyzer_types.construct_analyzer(action,
analyzer_config)
if disable_ctu:
# WARNING! can be called only on ClangSA
# Needs to be called before construct_analyzer_cmd
source_analyzer.disable_ctu()
if action.analyzer_type == ClangSA.ANALYZER_NAME and \
statistics_data:
# WARNING! Statistical checkers are only supported by Clang
# Static Analyzer right now.
stats_dir = statistics_data['stats_out_dir']
# WARNING Because both statistical checkers use the same config
# directory it is enough to add it only once. This might change later.
# Configuration arguments should be added before the checkers are
# enabled.
stats_cfg = \
SpecialReturnValueCollector.checker_analyze_cfg(stats_dir)
if os.path.exists(SpecialReturnValueCollector.stats_file(stats_dir)):
source_analyzer.add_checker_config(stats_cfg)
else:
LOG.debug('No checker statistics file was found for %s',
SpecialReturnValueCollector.checker_analyze)
# Source is the currently analyzed source file
# there can be more in one buildaction.
source_analyzer.source_file = action.source
# The result handler for analysis is an empty result handler
# which only returns metadata, but can't process the results.
rh = source_analyzer.construct_result_handler(action,
output_dir,
skip_handlers)
# NOTICE!
# The currently analyzed source file needs to be set before the
# analyzer command is constructed.
# The analyzer output file is based on the currently
# analyzed source.
rh.analyzed_source_file = action.source
return source_analyzer, rh
def handle_success(
rh, result_file, result_base, skip_handlers,
rs_handler: ReviewStatusHandler,
capture_analysis_output, success_dir
):
"""
Result postprocessing is required if the analysis was
successful (mainly clang tidy output conversion is done).
Skipping reports for header files is done here too.
"""
if capture_analysis_output:
save_output(os.path.join(success_dir, result_base),
rh.analyzer_stdout, rh.analyzer_stderr)
rh.postprocess_result(skip_handlers, rs_handler)
# Generated reports will be handled separately at store.
save_metadata(result_file, rh.analyzer_result_file,
rh.analyzed_source_file)
def handle_reproducer(source_analyzer, rh, zip_file, actions_map):
"""
If the analysis fails a debug zip is packed together which contains
build, analysis information and source files to be able to
reproduce the failed analysis.
"""
other_files = set()
action = rh.buildaction
try:
LOG.debug("Fetching other dependent files from analyzer output...")
other_files.update(
source_analyzer.get_analyzer_mentioned_files(
rh.analyzer_stdout))
other_files.update(
source_analyzer.get_analyzer_mentioned_files(
rh.analyzer_stderr))
except Exception as ex:
LOG.debug("Couldn't generate list of other files "
"from analyzer output:")
LOG.debug(str(ex))
LOG.debug("Collecting debug data")
buildactions = [{
'file': action.source,
'command': action.original_command,
'directory': action.directory}]
for of in other_files:
mentioned_file = os.path.abspath(os.path.join(action.directory, of))
key = mentioned_file, action.target
mentioned_file_action = actions_map.get(key)
if mentioned_file_action is not None:
buildactions.append({
'file': mentioned_file_action.source,
'command': mentioned_file_action.original_command,
'directory': mentioned_file_action.directory})
else:
LOG.debug("Could not find %s in build actions.", key)
from tu_collector import tu_collector
tu_collector.zip_tu_files(zip_file, buildactions)
# TODO: What about the dependencies of the other_files?
tu_collector.add_sources_to_zip(
zip_file,
[os.path.join(action.directory, path) for path in other_files])
with zipfile.ZipFile(zip_file, 'a') as archive:
LOG.debug("[ZIP] Writing analyzer STDOUT to /stdout")
archive.writestr("stdout", rh.analyzer_stdout)
LOG.debug("[ZIP] Writing analyzer STDERR to /stderr")
archive.writestr("stderr", rh.analyzer_stderr)
LOG.debug("[ZIP] Writing extra information...")
archive.writestr("build-action", action.original_command)
archive.writestr(
"analyzer-command",
' '.join([shlex.quote(x) for x in rh.analyzer_cmd]),
)
archive.writestr("return-code", str(rh.analyzer_returncode))
toolchain = gcc_toolchain.toolchain_in_args(
shlex.split(action.original_command))
if toolchain:
archive.writestr("gcc-toolchain-path", toolchain)
compiler_info_file = os.path.join(rh.workspace, 'compiler_info.json')
if os.path.exists(compiler_info_file):
archive.write(compiler_info_file, "compiler_info.json")
LOG.debug("ZIP file written at '%s'", zip_file)
def handle_failure(
source_analyzer, rh, zip_file, result_base, actions_map, skip_handlers,
rs_handler: ReviewStatusHandler
):
"""
If the analysis fails a debug zip is packed together which contains
build, analysis information and source files to be able to
reproduce the failed analysis.
"""
handle_reproducer(source_analyzer, rh, zip_file, actions_map)
# In case of compiler errors the error message still needs to be collected
# from the standard output by this postprocess phase so we can present them
# as CodeChecker reports.
checks = source_analyzer.config_handler.checks()
state = checks.get('clang-diagnostic-error', (CheckerState.enabled, ''))[0]
if state == CheckerState.enabled:
rh.postprocess_result(skip_handlers, rs_handler)
# Remove files that successfully analyzed earlier on.
plist_file = result_base + ".plist"
if os.path.exists(plist_file):
os.remove(plist_file)
def kill_process_tree(parent_pid, recursive=False):
"""Stop the process tree try it gracefully first.
Try to stop the parent and child processes gracefuly
first if they do not stop in time send a kill signal
to every member of the process tree.
There is a similar function in the web part please
consider to update that in case of changing this.
"""
proc = psutil.Process(parent_pid)
children = proc.children(recursive)
# Send a SIGTERM (Ctrl-C) to the main process
proc.terminate()
# If children processes don't stop gracefully in time,
# slaughter them by force.
_, still_alive = psutil.wait_procs(children, timeout=5)
for p in still_alive:
p.kill()
# Wait until this process is running.
n = 0
timeout = 10
while proc.is_running():
if n > timeout:
LOG.warning("Waiting for process %s to stop has been timed out"
"(timeout = %s)! Process is still running!",
parent_pid, timeout)
break
time.sleep(1)
n += 1
def setup_process_timeout(proc, timeout,
failure_callback=None):
"""
Setup a timeout on a process. After `timeout` seconds, the process is
killed by `signal_at_timeout` signal.
:param proc: The subprocess.Process object representing the process to
attach the watcher to.
:param timeout: The timeout the process is allowed to run for, in seconds.
This timeout is counted down some moments after calling
setup_process_timeout(), and due to OS scheduling, might not be exact.
:param failure_callback: An optional function which is called when the
process is killed by the timeout. This is called with no arguments
passed.
:return: A function is returned which should be called when the client code
(usually via subprocess.Process.wait() or
subprocess.Process.communicate())) figures out that the called process
has terminated. (See below for what this called function returns.)
"""
# The watch dict encapsulated the captured variables for the timeout watch.
watch = {'pid': proc.pid,
'timer': None, # Will be created later.
'counting': False,
'killed': False}
def __kill():
"""
Helper function to execute the killing of a hung process.
"""
LOG.debug("Process %s has ran for too long, killing it!",
watch['pid'])
watch['counting'] = False
watch['killed'] = True
kill_process_tree(watch['pid'], True)
if failure_callback:
failure_callback()
timer = Timer(timeout, __kill)
watch['timer'] = timer
LOG.debug("Setup timeout of %s for PID %s", proc.pid, timeout)
timer.start()
watch['counting'] = True
def __cleanup_timeout():
"""
Stops the timer associated with the timeout watch if the process
finished within the grace period.
Due to race conditions and the possibility of the OS, or another
process also using signals to kill the watched process in particular,
it is possible that checking subprocess.Process.returncode on the
watched process is not enough to see if the timeout watched killed it,
or something else.
(Note: returncode is -N where N is the signal's value, but only on Unix
systems!)
It is safe to call this function multiple times to check for the result
of the watching.
:return: Whether or not the process was killed by the watcher. If this
is False, the process could have finished gracefully, or could have
been destroyed by other means.
"""
if watch['counting'] and not watch['killed'] and watch['timer']:
watch['timer'].cancel()
watch['timer'] = None
watch['counting'] = False
return watch['killed']
return __cleanup_timeout
def collect_ctu_involved_files(result_handler, source_analyzer, output_dir):
"""
This function collects the list of source files involved by CTU analysis.
The list of files are written to output_dir.
"""
if source_analyzer.ANALYZER_NAME != ClangSA.ANALYZER_NAME:
return
involved_files = set()
involved_files.update(source_analyzer.get_analyzer_mentioned_files(
result_handler.analyzer_stdout))
involved_files.update(source_analyzer.get_analyzer_mentioned_files(
result_handler.analyzer_stderr))
out = os.path.join(output_dir, result_handler.analyzer_action_str)
if involved_files:
with open(out, 'w', encoding='utf-8', errors='ignore') as f:
f.write('\n'.join(involved_files))
elif os.path.exists(out):
os.remove(out)
def check(check_data):
"""
Invoke clang with an action which called by processes.
Different analyzer object belongs to for each build action.
skiplist handler is None if no skip file was configured.
"""
actions_map, action, analyzer_config, \
output_dir, skip_handlers, rs_handler, quiet_output_on_stdout, \
capture_analysis_output, generate_reproducer, analysis_timeout, \
ctu_reanalyze_on_failure, \
output_dirs, statistics_data = check_data
failed_dir = output_dirs["failed"]
success_dir = output_dirs["success"]
reproducer_dir = output_dirs["reproducer"]
try:
# If one analysis fails the check fails.
return_codes = 0
reanalyzed = False
result_file = ''
if analyzer_config is None:
raise Exception("Analyzer configuration is missing.")
source_analyzer, rh = prepare_check(action, analyzer_config,
output_dir,
skip_handlers, statistics_data)
reanalyzed = os.path.exists(rh.analyzer_result_file)
# Construct the analyzer cmd.
analyzer_cmd = source_analyzer.construct_analyzer_cmd(rh)
# The analyzer invocation calls __create_timeout as a callback
# when the analyzer starts. This callback creates the timeout
# watcher over the analyzer process, which in turn returns a
# function, that can later be used to check if the analyzer quit
# because we killed it due to a timeout.
#
# We need to capture the "function pointer" returned by
# setup_process_timeout as reference, so that we may call it
# later. To work around scoping issues, we use a list here so the
# "function pointer" is captured by reference.
timeout_cleanup = [lambda: False]
if analysis_timeout and analysis_timeout > 0:
def __create_timeout(analyzer_process):
"""
Once the analyzer process is started, this method is
called. Set up a timeout for the analysis.
"""
timeout_cleanup[0] = setup_process_timeout(
analyzer_process, analysis_timeout)
else:
def __create_timeout(analyzer_process):
# If no timeout is given by the client, this callback
# shouldn't do anything.
pass
result_file_exists = os.path.exists(rh.analyzer_result_file)
# Fills up the result handler with the analyzer information.
source_analyzer.analyze(analyzer_cmd, rh, __create_timeout)
# If execution reaches this line, the analyzer process has quit.
if timeout_cleanup[0]():
LOG.warning("Analyzer ran too long, exceeding time limit "
"of %d seconds.", analysis_timeout)
LOG.warning("Considering this analysis as failed...")
rh.analyzer_returncode = -1
rh.analyzer_stderr = (">>> CodeChecker: Analysis timed out "
"after {0} seconds. <<<\n{1}") \
.format(analysis_timeout, rh.analyzer_stderr)
source_analyzer.post_analyze(rh)
# If source file contains escaped spaces ("\ " tokens), then
# clangSA writes the plist file with removing this escape
# sequence, whereas clang-tidy does not. We rewrite the file
# names to contain no escape sequences for every analyzer.
result_file = rh.analyzer_result_file.replace(r'\ ', ' ')
result_base = os.path.basename(result_file)
ctu_active = is_ctu_active(source_analyzer)
zip_suffix = '_CTU' if ctu_active else ''
failure_type = "_unknown"
if rh.analyzer_returncode == 1:
failure_type = "_compile_error"
elif rh.analyzer_returncode == 254:
failure_type = "_crash"
elif rh.analyzer_returncode == 0:
failure_type = ""
zip_file = result_base + zip_suffix + failure_type + '.zip'
failed_zip_file = os.path.join(failed_dir, zip_file)
reproducer_zip_file = os.path.join(reproducer_dir, zip_file)
return_codes = rh.analyzer_returncode
source_file_name = os.path.basename(action.source)
# Remove the previously generated .zip files.
if os.path.exists(failed_zip_file):
os.remove(failed_zip_file)
if os.path.exists(reproducer_zip_file):
os.remove(reproducer_zip_file)
def handle_analysis_result(success, zip_file=zip_file):
"""
Extra actions after executing the analysis. In case of success the
clang-tidy output is transformed to reports. In case of failure the
necessary files are collected for debugging. These .zip files can
also be generated by --generate-reproducer flag.
"""
if generate_reproducer:
handle_reproducer(source_analyzer, rh,
os.path.join(reproducer_dir, zip_file),
actions_map)
if success:
handle_success(rh, result_file, result_base,
skip_handlers, rs_handler,
capture_analysis_output, success_dir)
elif not generate_reproducer:
handle_failure(source_analyzer, rh,
os.path.join(failed_dir, zip_file),
result_base, actions_map, skip_handlers,
rs_handler)
if rh.analyzer_returncode == 0:
handle_analysis_result(success=True)
LOG.info("[%d/%d] %s analyzed %s successfully.",
progress_checked_num.value, progress_actions.value,
action.analyzer_type, source_file_name)
if result_file_exists:
LOG.debug("Previous analysis results in '%s' has been "
"overwritten.", rh.analyzer_result_file)
else:
LOG.error("Analyzing %s with %s %s failed!",
source_file_name,
action.analyzer_type,
"CTU" if ctu_active else "")
if not quiet_output_on_stdout:
LOG.error("\n%s", rh.analyzer_stdout)
LOG.error("\n%s", rh.analyzer_stderr)
handle_analysis_result(success=False)
if ctu_active and ctu_reanalyze_on_failure:
LOG.error("Try to reanalyze without CTU")
# Try to reanalyze with CTU disabled.
source_analyzer, rh = \
prepare_check(action, analyzer_config,
output_dir,
skip_handlers, statistics_data,
True)
reanalyzed = os.path.exists(rh.analyzer_result_file)
# Construct the analyzer cmd.
analyzer_cmd = source_analyzer.construct_analyzer_cmd(rh)
# Fills up the result handler with
# the analyzer information.
source_analyzer.analyze(analyzer_cmd, rh)
return_codes = rh.analyzer_returncode
if rh.analyzer_returncode == 0:
handle_analysis_result(success=True)
LOG.info("[%d/%d] %s analyzed %s without"
" CTU successfully.",
progress_checked_num.value,
progress_actions.value,
action.analyzer_type,
source_file_name)
if result_file_exists:
LOG.debug("Previous analysis results in '%s' has "
"been overwritten.",
rh.analyzer_result_file)
else:
LOG.error("Analyzing '%s' with %s without CTU failed.",
source_file_name, action.analyzer_type)
handle_analysis_result(success=False,
zip_file=result_base + '.zip')
collect_ctu_involved_files(rh, source_analyzer,
output_dirs['ctu_connections'])
if not quiet_output_on_stdout:
if rh.analyzer_returncode:
LOG.error('\n%s', rh.analyzer_stdout)
LOG.error('\n%s', rh.analyzer_stderr)
else:
LOG.debug_analyzer('\n%s', rh.analyzer_stdout)
LOG.debug_analyzer('\n%s', rh.analyzer_stderr)
progress_checked_num.value += 1
return return_codes, False, reanalyzed, action.analyzer_type, \
result_file, action.source
except Exception as e:
LOG.debug_analyzer(str(e))
traceback.print_exc(file=sys.stdout)
return 1, False, reanalyzed, action.analyzer_type, None, \
action.source
def skip_cpp(compile_actions, skip_handlers):
"""If there is no skiplist handler there was no skip list file in
the command line.
C++ file skipping is handled here.
"""
if not skip_handlers:
return compile_actions, []
analyze = []
skip = []
for compile_action in compile_actions:
if skip_handlers and skip_handlers.should_skip(compile_action.source):
skip.append(compile_action)
else:
analyze.append(compile_action)
return analyze, skip
def start_workers(actions_map, actions, analyzer_config_map,
jobs, output_path, skip_handlers,
rs_handler: ReviewStatusHandler, metadata_tool,
quiet_analyze, capture_analysis_output, generate_reproducer,
timeout, ctu_reanalyze_on_failure, statistics_data, manager,
compile_cmd_count):
"""
Start the workers in the process pool.
For every build action there is worker which makes the analysis.
"""
# pylint: disable=no-member multiprocess module members.
# Handle SIGINT to stop this script running.
def signal_handler(signum, frame):
try:
pool.terminate()
pool.join()
manager.shutdown()
except Exception as e:
LOG.error("Failed to clean up after the pool!:\n")
LOG.error(e)
raise
finally:
sys.exit(128 + signum)
actions, skipped_actions = skip_cpp(actions, skip_handlers)
# Start checking parallel.
checked_var = multiprocess.Value('i', 1)
actions_num = multiprocess.Value('i', len(actions))
pool = multiprocess.Pool(jobs,
initializer=init_worker,
initargs=(checked_var, actions_num))
signal.signal(signal.SIGINT, signal_handler)
# If the analysis has failed, we help debugging.
failed_dir = os.path.join(output_path, "failed")
if not os.path.exists(failed_dir):
os.makedirs(failed_dir)
# Analysis was successful processing results.
success_dir = os.path.join(output_path, "success")
if not os.path.exists(success_dir):
os.makedirs(success_dir)
# Similar to failed dir, but generated both in case of success and failure.
reproducer_dir = os.path.join(output_path, "reproducer")
if not os.path.exists(reproducer_dir) and generate_reproducer:
os.makedirs(reproducer_dir)
# Cppcheck raw output directory.
cppcheck_dir = os.path.join(output_path, "cppcheck")
if not os.path.exists(cppcheck_dir):
os.makedirs(cppcheck_dir)
# Collect what other TUs were involved during CTU analysis.
ctu_connections_dir = os.path.join(output_path, "ctu_connections")
if not os.path.exists(ctu_connections_dir):
os.makedirs(ctu_connections_dir)
output_dirs = {'success': success_dir,
'failed': failed_dir,
'reproducer': reproducer_dir,
'ctu_connections': ctu_connections_dir}
analyzed_actions = [(actions_map,
build_action,
analyzer_config_map.get(build_action.analyzer_type),
output_path,
skip_handlers,
rs_handler,
quiet_analyze,
capture_analysis_output,
generate_reproducer,
timeout,
ctu_reanalyze_on_failure,
output_dirs,
statistics_data)
for build_action in actions]
if analyzed_actions:
try:
# Workaround, equivalent of map.
# The main script does not get signal
# while map or map_async function is running.
# It is a python bug, this does not happen if a timeout is
# specified, then receive the interrupt immediately.
# FIXME: Ensure all shared data structures are wrapped in manager
# proxy objects before passing them to other processes via
# map_async.
# Note that even deep-copying is known to be insufficient.
timeout = 3155760 if sys.platform == 'win32' else 31557600
pool.map_async(check,
analyzed_actions,
1,
callback=lambda results: worker_result_handler(
results, metadata_tool, output_path)
).get(timeout)
pool.close()
except Exception:
pool.terminate()
raise
finally:
pool.join()
else:
pool.close()
pool.join()
LOG.info("----==== Summary ====----")
for skp in skipped_actions:
LOG.debug_analyzer("%s is skipped", skp.source)
LOG.info("Total analyzed compilation commands: %d",
compile_cmd_count.analyze)
# Some compile commands are skipped during log processing, if nothing
# is skipped there for pre-analysis step, files will be skipped only
# during analysis.
if skipped_actions or compile_cmd_count.skipped:
LOG.info("Skipped compilation commands: %d",
compile_cmd_count.skipped + len(skipped_actions))
LOG.info("----=================----")
if not os.listdir(success_dir):
shutil.rmtree(success_dir)
if not os.listdir(failed_dir):
shutil.rmtree(failed_dir)