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
« 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/>.
20import sys
21import argparse
22import os.path
23import subprocess
24import re
25from typing import Optional
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
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\]$"
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)
52ap = argparse.ArgumentParser()
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
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"))
91 options = ap.parse_args()
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)
106 # Test if the clang-tidy can be used
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 )
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
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)
136 subprocess_args += file_list
138 rv = subprocess.run(
139 subprocess_args,
140 stdout=subprocess.PIPE,
141 stderr=subprocess.PIPE,
142 encoding="UTF-8",
143 check=False,
144 )
146 if rv.returncode != 0:
147 print(rv.stdout)
148 print()
149 print(rv.stderr)
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
159 db = {}
160 tag_location_generator = TagLocationGenerator()
162 for line in rv.stdout.splitlines():
163 if not line.endswith("[lobster-tracing]"):
164 continue
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)
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)
180 continue
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)
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)
196 db[tag.key()].just_up.append(reason)
198 continue
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)
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)
214 db[tag.key()].add_tracing_target(Tracing_Tag("req", ref))
216 continue
218 print("could not parse line")
219 print(">", line)
220 return 1
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))
227 else:
228 lobster_write(sys.stdout, Implementation, "lobster_cpp", db.values())
229 print()
232if __name__ == "__main__":
233 sys.exit(main())