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

87 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-18 11:07 +0000

1#!/usr/bin/env python3 

2# 

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

4# Copyright (C) 2023-2025 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 

20from argparse import Namespace 

21import os.path 

22import subprocess 

23import re 

24from typing import Optional, Sequence 

25 

26from lobster.common.items import Tracing_Tag, Implementation 

27from lobster.common.multi_file_input_config import Config 

28from lobster.tools.cpp.implementation_builder import ImplementationBuilder 

29from lobster.common.multi_file_input_tool import create_worklist, MultiFileInputTool 

30 

31 

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

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

34NAME_PATTERN = r"([a-zA-Z0-9_:~^()&\s<>,=*!+-.|[\]/\"]+)" 

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 +([^,\n]+(?:\s*,\s*[^,\n]+)*) +" % (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 

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

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

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

55 return line.split("[")[-1].rstrip("]") 

56 return None 

57 

58 

59class CppTool(MultiFileInputTool): 

60 def __init__(self): 

61 super().__init__( 

62 name = "cpp", 

63 description = "Extract tracing tags from C++ using clang-tidy", 

64 extensions=("cpp", "cc", "c", "h"), 

65 official = True, 

66 ) 

67 self._argument_parser.add_argument( 

68 "--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 ) 

74 self._argument_parser.add_argument( 

75 "--compile-commands", 

76 metavar="FILE", 

77 default=None, 

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

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

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

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

82 "documentation for more details."), 

83 ) 

84 self._argument_parser.add_argument( 

85 "--skip-clang-errors", 

86 default=[], 

87 nargs="*", 

88 metavar="FINDINGS", 

89 help="List of all clang-tidy errors to ignore.", 

90 ) 

91 

92 def _add_config_argument(self): 

93 # This tool does not use a config file 

94 pass 

95 

96 def _run_impl(self, options: Namespace) -> int: 

97 config = Config( 

98 inputs=None, 

99 inputs_from_file=None, 

100 extensions=self._extensions, 

101 exclude_patterns=None, 

102 schema=Implementation, 

103 ) 

104 file_list = create_worklist(config, options.dir_or_files) 

105 clang_tidy_path = os.path.expanduser(options.clang_tidy) 

106 

107 # Test if the clang-tidy can be used 

108 rv = subprocess.run( 

109 [ 

110 clang_tidy_path, 

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 clang_tidy_path, 

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(f"Ignoring clang-tidy error {clang_error}") 

156 else: 

157 print("Found clang-tidy error: ", clang_error) 

158 return 1 

159 

160 db = {} 

161 implementation_builder = ImplementationBuilder() 

162 

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

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

165 continue 

166 

167 match = re.match(RE_NOTAGS, line) 

168 if match: 

169 impl = implementation_builder.from_match(match) 

170 assert impl.tag.key() not in db 

171 db[impl.tag.key()] = impl 

172 continue 

173 

174 match = re.match(RE_JUST, line) 

175 if match: 

176 impl = implementation_builder.from_match_if_new(db, match) 

177 impl.just_up.append( 

178 match.group(implementation_builder.REASON_GROUP_NUM), 

179 ) 

180 continue 

181 

182 match = re.match(RE_TAGS, line) 

183 if match: 

184 impl = implementation_builder.from_match_if_new(db, match) 

185 all_tags = match.group(implementation_builder.REFERENCE_GROUP_NUM) 

186 for tag in re.split(r"[, ]+", all_tags.strip()): 

187 if tag: 

188 impl.add_tracing_target( 

189 Tracing_Tag( 

190 "req", 

191 tag, 

192 ), 

193 ) 

194 continue 

195 

196 print("could not parse line") 

197 print(">", line) 

198 return 1 

199 

200 self._write_output( 

201 schema=config.schema, 

202 out_file=options.out, 

203 items=db.values(), 

204 ) 

205 

206 return 0 

207 

208 

209def main(args: Optional[Sequence[str]] = None) -> int: 

210 return CppTool().run(args)