Coverage for lobster/tools/cpp/cpp.py: 0%

104 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-26 14:55 +0000

1#!/usr/bin/env python3 

2# 

3# lobster_cpp - Extract C/C++ tracing tags for LOBSTER 

4# Copyright (C) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) 

5# 

6# This program is free software: you can redistribute it and/or modify 

7# it under the terms of the GNU Affero General Public License as 

8# published by the Free Software Foundation, either version 3 of the 

9# License, or (at your option) any later version. 

10# 

11# This program is distributed in the hope that it will be useful, but 

12# WITHOUT ANY WARRANTY; without even the implied warranty of 

13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 

14# Affero General Public License for more details. 

15# 

16# You should have received a copy of the GNU Affero General Public 

17# License along with this program. If not, see 

18# <https://www.gnu.org/licenses/>. 

19 

20import sys 

21import argparse 

22import os.path 

23import subprocess 

24import re 

25from typing import Optional 

26 

27from lobster.items import Tracing_Tag, Implementation 

28from lobster.io import lobster_write 

29from lobster.tools.cpp.tag_location_generator import TagLocationGenerator 

30from lobster.version import get_version 

31 

32FILE_LINE_PATTERN = r"(.*):(\d+):\d+:" 

33KIND_PATTERN = r"(function|main function|method)" 

34NAME_PATTERN = r"([a-zA-Z0-9_:~]+)" 

35PREFIX = "^%s warning:" % FILE_LINE_PATTERN 

36SUFFIX = r"\[lobster-tracing\]$" 

37 

38RE_NOTAGS = (PREFIX + " " + 

39 r"%s %s has no tracing tags" % (KIND_PATTERN, 

40 NAME_PATTERN) + 

41 " " + SUFFIX) 

42RE_TAGS = (PREFIX + " " + 

43 r"%s %s traces to +(.+) +" % (KIND_PATTERN, 

44 NAME_PATTERN) + 

45 SUFFIX) 

46RE_JUST = (PREFIX + " " + 

47 r"%s %s exempt from tracing: +(.+) +" % (KIND_PATTERN, 

48 NAME_PATTERN) + 

49 SUFFIX) 

50 

51 

52ap = argparse.ArgumentParser() 

53 

54 

55def extract_clang_finding_name(line: str) -> Optional[str]: 

56 """extracts the name of the clang finding from the end of the line""" 

57 if line.endswith("]") and ("[" in line): 

58 return line.split("[")[-1] 

59 return None 

60 

61 

62@get_version(ap) 

63def main(): 

64 # lobster-trace: cpp_req.Dummy_Requirement 

65 ap.add_argument("files", 

66 nargs="+", 

67 metavar="FILE|DIR") 

68 ap.add_argument("--clang-tidy", 

69 default="clang-tidy", 

70 metavar="FILE", 

71 help=("use the specified clang-tidy; by default we" 

72 " pick the one on PATH")) 

73 ap.add_argument("--compile-commands", 

74 metavar="FILE", 

75 default=None, 

76 help=("Path to the compile command database for all targets for " 

77 "'clang tidy', or none to use the default behavior of " 

78 "'clang tidy'. This is equal to calling 'clang tidy' " 

79 "directly with its '-p' option. Refer to its official " 

80 "documentation for more details.")) 

81 ap.add_argument("--skip-clang-errors", 

82 default=[], 

83 nargs="*", 

84 metavar="FINDINGS", 

85 help="List of all clang-tidy errors to ignore.") 

86 ap.add_argument("--out", 

87 default=None, 

88 help=("write output to this file; otherwise output to" 

89 " to stdout")) 

90 

91 options = ap.parse_args() 

92 

93 file_list = [] 

94 for item in options.files: 

95 if os.path.isfile(item): 

96 file_list.append(item) 

97 elif os.path.isdir(item): 

98 for path, _, files in os.walk(item): 

99 for filename in files: 

100 _, ext = os.path.splitext(filename) 

101 if ext in (".cpp", ".cc", ".c", ".h"): 

102 file_list.append(os.path.join(path, filename)) 

103 else: 

104 ap.error("%s is not a file or directory" % item) 

105 

106 # Test if the clang-tidy can be used 

107 

108 rv = subprocess.run( 

109 [ 

110 os.path.expanduser(options.clang_tidy), 

111 "-checks=-*,lobster-tracing", 

112 "--list-checks", 

113 ], 

114 stdout=subprocess.PIPE, 

115 stderr=subprocess.PIPE, 

116 encoding="UTF-8", 

117 check=False, 

118 ) 

119 

120 if "No checks enabled." in rv.stderr: 

121 print("The provided clang-tidy does include the lobster-tracing check") 

122 print("> Please build from " 

123 "https://github.com/bmw-software-engineering/llvm-project") 

124 print("> Or make sure to provide the " 

125 "correct binary using the --clang-tidy flag") 

126 return 1 

127 

128 subprocess_args = [ 

129 os.path.expanduser(options.clang_tidy), 

130 "-checks=-*,lobster-tracing", 

131 ] 

132 if options.compile_commands: 

133 subprocess_args.append("-p") 

134 subprocess_args.append(options.compile_commands) 

135 

136 subprocess_args += file_list 

137 

138 rv = subprocess.run( 

139 subprocess_args, 

140 stdout=subprocess.PIPE, 

141 stderr=subprocess.PIPE, 

142 encoding="UTF-8", 

143 check=False, 

144 ) 

145 

146 if rv.returncode != 0: 

147 print(rv.stdout) 

148 print() 

149 print(rv.stderr) 

150 

151 for line in rv.stdout.splitlines(): 

152 if "error: " in line: 

153 clang_error = extract_clang_finding_name(line) 

154 if clang_error and (clang_error in options.skip_clang_errors): 

155 print("Ignoring clang-tidy error %s" % clang_error) 

156 else: 

157 return 1 

158 

159 db = {} 

160 tag_location_generator = TagLocationGenerator() 

161 

162 for line in rv.stdout.splitlines(): 

163 if not line.endswith("[lobster-tracing]"): 

164 continue 

165 

166 match = re.match(RE_NOTAGS, line) 

167 if match: 

168 filename, line_nr, kind, function_name = match.groups() 

169 line_nr = int(line_nr) 

170 tag = tag_location_generator.get_tag(filename, function_name, line_nr) 

171 

172 assert tag.key() not in db 

173 db[tag.key()] = Implementation( 

174 tag = tag, 

175 location = tag_location_generator.get_location(filename, line_nr), 

176 language = "C/C++", 

177 kind = kind, 

178 name = function_name) 

179 

180 continue 

181 

182 match = re.match(RE_JUST, line) 

183 if match: 

184 filename, line_nr, kind, function_name, reason = match.groups() 

185 line_nr = int(line_nr) 

186 tag = tag_location_generator.get_tag(filename, function_name, line_nr) 

187 

188 if tag.key() not in db: 

189 db[tag.key()] = Implementation( 

190 tag = tag, 

191 location = tag_location_generator.get_location(filename, line_nr), 

192 language = "C/C++", 

193 kind = kind, 

194 name = function_name) 

195 

196 db[tag.key()].just_up.append(reason) 

197 

198 continue 

199 

200 match = re.match(RE_TAGS, line) 

201 if match: 

202 filename, line_nr, kind, function_name, ref = match.groups() 

203 line_nr = int(line_nr) 

204 tag = tag_location_generator.get_tag(filename, function_name, line_nr) 

205 

206 if tag.key() not in db: 

207 db[tag.key()] = Implementation( 

208 tag = tag, 

209 location = tag_location_generator.get_location(filename, line_nr), 

210 language = "C/C++", 

211 kind = kind, 

212 name = function_name) 

213 

214 db[tag.key()].add_tracing_target(Tracing_Tag("req", ref)) 

215 

216 continue 

217 

218 print("could not parse line") 

219 print(">", line) 

220 return 1 

221 

222 if options.out: 

223 with open(options.out, "w", encoding="UTF-8") as fd: 

224 lobster_write(fd, Implementation, "lobster_cpp", db.values()) 

225 print("Written output for %u items to %s" % (len(db), options.out)) 

226 

227 else: 

228 lobster_write(sys.stdout, Implementation, "lobster_cpp", db.values()) 

229 print() 

230 

231 

232if __name__ == "__main__": 

233 sys.exit(main())