1#!/usr/bin/env python 2# 3#===- check-analyzer-fixit.py - Static Analyzer test helper ---*- python -*-===# 4# 5# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 6# See https://llvm.org/LICENSE.txt for license information. 7# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 8# 9#===------------------------------------------------------------------------===# 10# 11# This file copy-pasted mostly from the Clang-Tidy's 'check_clang_tidy.py'. 12# 13#===------------------------------------------------------------------------===# 14 15r""" 16Clang Static Analyzer test helper 17================================= 18 19This script runs the Analyzer in fix-it mode and verify fixes, warnings, notes. 20 21Usage: 22 check-analyzer-fixit.py <source-file> <temp-file> [analyzer arguments] 23 24Example: 25 // RUN: %check-analyzer-fixit %s %t -analyzer-checker=core 26""" 27 28import argparse 29import os 30import re 31import subprocess 32import sys 33 34 35def write_file(file_name, text): 36 with open(file_name, 'w') as f: 37 f.write(text) 38 39 40def run_test_once(args, extra_args): 41 input_file_name = args.input_file_name 42 temp_file_name = args.temp_file_name 43 clang_analyzer_extra_args = extra_args 44 45 file_name_with_extension = input_file_name 46 _, extension = os.path.splitext(file_name_with_extension) 47 if extension not in ['.c', '.hpp', '.m', '.mm']: 48 extension = '.cpp' 49 temp_file_name = temp_file_name + extension 50 51 with open(input_file_name, 'r') as input_file: 52 input_text = input_file.read() 53 54 # Remove the contents of the CHECK lines to avoid CHECKs matching on 55 # themselves. We need to keep the comments to preserve line numbers while 56 # avoiding empty lines which could potentially trigger formatting-related 57 # checks. 58 cleaned_test = re.sub('// *CHECK-[A-Z0-9\-]*:[^\r\n]*', '//', input_text) 59 write_file(temp_file_name, cleaned_test) 60 61 original_file_name = temp_file_name + ".orig" 62 write_file(original_file_name, cleaned_test) 63 64 try: 65 builtin_include_dir = subprocess.check_output( 66 ['clang', '-print-file-name=include'], stderr=subprocess.STDOUT).decode() 67 except subprocess.CalledProcessError as e: 68 print('Cannot print Clang include directory: ' + e.output.decode()) 69 70 builtin_include_dir = os.path.normpath(builtin_include_dir) 71 72 args = (['clang', '-cc1', '-internal-isystem', builtin_include_dir, 73 '-nostdsysteminc', '-analyze', '-analyzer-constraints=range', 74 '-analyzer-config', 'apply-fixits=true'] 75 + clang_analyzer_extra_args + ['-verify', temp_file_name]) 76 77 print('Running ' + str(args) + '...') 78 79 try: 80 clang_analyzer_output = \ 81 subprocess.check_output(args, stderr=subprocess.STDOUT).decode() 82 except subprocess.CalledProcessError as e: 83 print('Clang Static Analyzer test failed:\n' + e.output.decode()) 84 raise 85 86 print('----------------- Clang Static Analyzer output -----------------\n' + 87 clang_analyzer_output + 88 '\n--------------------------------------------------------------') 89 90 try: 91 diff_output = subprocess.check_output( 92 ['diff', '-u', original_file_name, temp_file_name], 93 stderr=subprocess.STDOUT) 94 except subprocess.CalledProcessError as e: 95 diff_output = e.output 96 97 print('----------------------------- Fixes ----------------------------\n' + 98 diff_output.decode() + 99 '\n--------------------------------------------------------------') 100 101 try: 102 subprocess.check_output( 103 ['FileCheck', '-input-file=' + temp_file_name, input_file_name, 104 '-check-prefixes=CHECK-FIXES', '-strict-whitespace'], 105 stderr=subprocess.STDOUT) 106 except subprocess.CalledProcessError as e: 107 print('FileCheck failed:\n' + e.output.decode()) 108 raise 109 110 111def main(): 112 parser = argparse.ArgumentParser() 113 parser.add_argument('input_file_name') 114 parser.add_argument('temp_file_name') 115 116 args, extra_args = parser.parse_known_args() 117 run_test_once(args, extra_args) 118 119 120if __name__ == '__main__': 121 main() 122