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

87 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-05-12 15:02 +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 = f"^{FILE_LINE_PATTERN} warning:" 

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

37 

38RE_NOTAGS = (PREFIX + " " + 

39 rf"{KIND_PATTERN} {NAME_PATTERN} has no tracing tags" + 

40 " " + SUFFIX) 

41RE_TAGS = (PREFIX + " " + 

42 rf"{KIND_PATTERN} {NAME_PATTERN} traces to +([^,\n]+(?:\s*,\s*[^,\n]+)*) +" + 

43 SUFFIX) 

44RE_JUST = (PREFIX + " " + 

45 rf"{KIND_PATTERN} {NAME_PATTERN} exempt from tracing: +(.+) +" + 

46 SUFFIX) 

47 

48 

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

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

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

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

53 return None 

54 

55 

56class CppTool(MultiFileInputTool): 

57 def __init__(self): 

58 super().__init__( 

59 name = "cpp", 

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

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

62 official = True, 

63 ) 

64 self._argument_parser.add_argument( 

65 "--clang-tidy", 

66 default="clang-tidy", 

67 metavar="FILE", 

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

69 " pick the one on PATH"), 

70 ) 

71 self._argument_parser.add_argument( 

72 "--compile-commands", 

73 metavar="FILE", 

74 default=None, 

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

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

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

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

79 "documentation for more details."), 

80 ) 

81 self._argument_parser.add_argument( 

82 "--skip-clang-errors", 

83 default=[], 

84 nargs="*", 

85 metavar="FINDINGS", 

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

87 ) 

88 

89 def _add_config_argument(self): 

90 # This tool does not use a config file 

91 pass 

92 

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

94 config = Config( 

95 inputs=None, 

96 inputs_from_file=None, 

97 extensions=self._extensions, 

98 exclude_patterns=None, 

99 schema=Implementation, 

100 ) 

101 file_list = create_worklist(config, options.dir_or_files) 

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

103 

104 # Test if the clang-tidy can be used 

105 rv = subprocess.run( 

106 [ 

107 clang_tidy_path, 

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

109 "--list-checks", 

110 ], 

111 stdout=subprocess.PIPE, 

112 stderr=subprocess.PIPE, 

113 encoding="UTF-8", 

114 check=False, 

115 ) 

116 

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

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

119 print("> Please build from " 

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

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

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

123 return 1 

124 

125 subprocess_args = [ 

126 clang_tidy_path, 

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

128 ] 

129 if options.compile_commands: 

130 subprocess_args.append("-p") 

131 subprocess_args.append(options.compile_commands) 

132 

133 subprocess_args += file_list 

134 

135 rv = subprocess.run( 

136 subprocess_args, 

137 stdout=subprocess.PIPE, 

138 stderr=subprocess.PIPE, 

139 encoding="UTF-8", 

140 check=False, 

141 ) 

142 

143 if rv.returncode != 0: 

144 print(rv.stdout) 

145 print() 

146 print(rv.stderr) 

147 

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

149 if "error: " in line: 

150 clang_error = extract_clang_finding_name(line) 

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

152 print(f"Ignoring clang-tidy error {clang_error}") 

153 else: 

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

155 return 1 

156 

157 db = {} 

158 implementation_builder = ImplementationBuilder() 

159 

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

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

162 continue 

163 

164 match = re.match(RE_NOTAGS, line) 

165 if match: 

166 impl = implementation_builder.from_match(match) 

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

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

169 continue 

170 

171 match = re.match(RE_JUST, line) 

172 if match: 

173 impl = implementation_builder.from_match_if_new(db, match) 

174 impl.just_up.append( 

175 match.group(implementation_builder.REASON_GROUP_NUM), 

176 ) 

177 continue 

178 

179 match = re.match(RE_TAGS, line) 

180 if match: 

181 impl = implementation_builder.from_match_if_new(db, match) 

182 all_tags = match.group(implementation_builder.REFERENCE_GROUP_NUM) 

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

184 if tag: 

185 impl.add_tracing_target( 

186 Tracing_Tag( 

187 "req", 

188 tag, 

189 ), 

190 ) 

191 continue 

192 

193 print("could not parse line") 

194 print(">", line) 

195 return 1 

196 

197 self._write_output( 

198 schema=config.schema, 

199 out_file=options.out, 

200 items=db.values(), 

201 ) 

202 

203 return 0 

204 

205 

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

207 return CppTool().run(args)